You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET MAUI中CollectionView重复添加项问题求助

解决.NET MAUI CollectionView滚动加载重复数据问题

异常现象

  • 首次加载1、2、3、4、5项,后续每次滚动仍重复添加1、2、3、4、5项;
  • 添加Task.Delay(1000)后,会先重复添加1、2、3、4、5项,再加载6、7、8、9、10项等;
  • 使用DisplayAlert()可避免重复添加,但这并非可行解决方案。

相关代码实现

CollectionView绑定代码

<CollectionView ItemsSource="{Binding Posts}"
                ItemTemplate="{StaticResource CommunityItemTemplateSelector}"
                RemainingItemsThresholdReachedCommand="{Binding PopulateCommand}"
                RemainingItemsThreshold="0">
    <!-- Using DataTemplates to populate -->
</CollectionView>

ViewModel代码

private CommunityService communityService { get; set; }

private int start = 0;
private int limit = 5;
private List<CommunityItem> posts { get; set; } = new();
public ObservableRangeCollection<CommunityItem> Posts { get; private set; } = new();

public Command OnAppearingCommand { get; private set; }
public Command PopulateCommand { get; private set; }

public CommunityViewModel(CommunityService communityService)
{
    this.communityService = communityService;

    OnAppearingCommand = new Command(Appearing);
    PopulateCommand = new Command(Populate);
}

private void Appearing()
{
    Posts.Clear();
    Populate();
}

private async void Populate()
{
    posts.Clear();

    await Task.Delay(250);

    posts = await communityService.GetPosts(start, limit);

    start += limit;

    if (posts.Count > 0)
        Posts.AddRange(posts);
}

CommunityService代码

public class CommunityService : BaseService
{
    public List<CommunityItem> List = new();

    public async Task<List<CommunityItem>> GetPosts(int start, int limit)
    {
        string range = "&start=" + start + "&limit=" + limit;

        try
        {
            var response = await http.GetAsync(this.uri + "/post" + range + this.key);

            if (response.IsSuccessStatusCode && response.Content is not null)
                List = await response.Content.ReadFromJsonAsync<List<CommunityItem>>();

            // Somewhat solving the problem:
            await Task.Delay(1000);

            // Solving it completely for some reason, but shows an alert:
            await Application.Current.MainPage.DisplayAlert("Debug", "Waiting", "OK");
        }
        catch (Exception e)
        {
            // Debug
        }

        return List;
    }
}

根本原因

  1. 异步命令无状态追踪:Populate方法使用async void,普通Command无法追踪异步操作的完成状态,导致CollectionView的加载命令被多次触发;
  2. 线程安全问题:CommunityService中使用类级别的List字段存储结果,异步请求时可能返回未更新的旧数据;
  3. 无加载状态控制:未限制同一时间只能发起一次加载请求,start变量还未更新就发起新请求,导致多次请求相同的分页参数。

解决方案

1. 改用异步命令追踪操作状态

使用AsyncRelayCommand(来自CommunityToolkit.Mvvm包)替代普通Command,它能正确处理异步方法的执行状态,避免重复触发。

2. 添加加载状态锁

在ViewModel中新增isLoading字段,确保同一时间只有一个加载请求在执行。

3. 修复Service的线程安全问题

移除类级别的List字段,每次请求创建新的列表对象,避免旧数据被复用。

修改后的代码

ViewModel(需安装CommunityToolkit.Mvvm NuGet包)

using CommunityToolkit.Mvvm.Input;

private CommunityService communityService { get; set; }

private int start = 0;
private int limit = 5;
public ObservableRangeCollection<CommunityItem> Posts { get; private set; } = new();
private bool isLoading;

public IAsyncCommand OnAppearingCommand { get; private set; }
public IAsyncCommand PopulateCommand { get; private set; }

public CommunityViewModel(CommunityService communityService)
{
    this.communityService = communityService;

    OnAppearingCommand = new AsyncRelayCommand(Appearing);
    PopulateCommand = new AsyncRelayCommand(Populate);
}

private async Task Appearing()
{
    isLoading = true;
    Posts.Clear();
    start = 0;
    await Populate();
    isLoading = false;
}

private async Task Populate()
{
    if (isLoading) return;
    isLoading = true;

    try
    {
        var newPosts = await communityService.GetPosts(start, limit);

        if (newPosts.Count > 0)
        {
            Posts.AddRange(newPosts);
            start += limit;
        }
        // 可选:当无新数据时,禁用加载命令
        // else PopulateCommand.CanExecute(false);
    }
    finally
    {
        isLoading = false;
    }
}

CommunityService

public class CommunityService : BaseService
{
    public async Task<List<CommunityItem>> GetPosts(int start, int limit)
    {
        string range = $"&start={start}&limit={limit}";
        var result = new List<CommunityItem>();

        try
        {
            var response = await http.GetAsync($"{this.uri}/post{range}{this.key}");

            if (response.IsSuccessStatusCode && response.Content is not null)
                result = await response.Content.ReadFromJsonAsync<List<CommunityItem>>() ?? new List<CommunityItem>();
        }
        catch (Exception e)
        {
            // 可添加日志记录异常信息
        }

        return result;
    }
}

额外优化建议

  • 在CollectionView的Footer中添加ActivityIndicator,绑定isLoading字段,显示加载状态;
  • 当返回的新数据为空时,禁用PopulateCommand,避免无效请求;
  • 可以添加下拉刷新功能,重置start并重新加载数据。

内容的提问来源于stack exchange,提问作者user7862339

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 09:00:18