c# - List.Add 未完美添加并行和异步

标签 c# async-await blazor-webassembly parallel.foreach .net-6.0

我有一个 Blazor WASM 应用程序,需要每秒调用一个 API,而不阻塞 UI。此代码演示了我如何尝试做到这一点:

List<int> testList = new();
testList.Add(1);
testList.Add(2);
testList.Add(3);
testList.Add(4);

List<int> emptyTestlist = new();

CancellationTokenSource cts;

Test();

void Test()
{
    Parallel.Invoke(async () =>
    {
        do
        {
            Console.WriteLine("Start");
            await Task.Delay(1000);
            await Test2();
            Console.WriteLine("END");
        } while (true);
    });
}
Console.ReadLine();

async ValueTask Test2()
{
    emptyTestlist.Clear();
    cts = new();
    await Parallel.ForEachAsync(testList, cts.Token, async (test, token) =>
    {
        await Test4(test);
    });
    foreach (var test in emptyTestlist)
    {
        await Test3(test);
    }
}

async Task Test4(int i)
{
    await Task.Delay(300);
    //Console.WriteLine("if I Add this console.WriteLine It's added perfectly");
    emptyTestlist.Add(i);
    Console.WriteLine($"from TEST4: {i}");
}

async Task Test3(int i)
{
    Console.WriteLine($"TEST3 {i}.");
    await Task.Delay(1000);
    Console.WriteLine($"TEST3 {i}, after 1sec");
}

如果我注释行 Console.WriteLine("if I Add this console.WriteLine It's added beautiful");,它不会完美添加。 (emptyTeSTList.Count 并不总是 4)。但是,如果我在 emptyTeSTList.Add(i) 之前添加 Console.WriteLine,它就会正常工作(emptyTeSTList.Count 始终为 4)。

不知道怎么解决。有什么问题吗?

最佳答案

轮询 API 的最简单方法是使用计时器:

@code {
    private List<Customer> custs=new List<Customer>();
    
    private System.Threading.Timer timer;

    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
        custs = await Http.GetFromJsonAsync<List<Customer>>(url);

        timer = new System.Threading.Timer(async _ =>
        {
            custs = await Http.GetFromJsonAsync<List<Customer>>("/api/customers");
            InvokeAsync(StateHasChanged); 
        }, null, 1000, 1000);
    }

在这种情况下,需要 InvokeAsync(StateHasChanged);,因为状态是从计时器线程修改的,而 Blazor 不知道数据已更改。

如果我们想将结果添加到列表中,则必须使用锁或线程安全集合,例如 ConcurrentQueue

@code {
    private ConcurrentQueue<Customer> custs=new ConcurrentQueue<Customer>();
    
    private System.Threading.Timer timer;

    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
        custs = await Http.GetFromJsonAsync<List<Customer>>(url);

        timer = new System.Threading.Timer(async _ =>
        {
            var results = await Http.GetFromJsonAsync<List<Customer>>("/api/customers");
            foreach(var c in results)
            {
                custs.Enqueue(c);
            }
            InvokeAsync(StateHasChanged); 
        }, null, 1000, 1000);
    }

每秒轮询一次 API,以防出现任何新数据,但效率并不高。最好让 API 使用 SignalR 通知客户端任何新数据。或Push Notifications

借用documentation example这足以从服务器接收消息:

@code {
    private HubConnection hubConnection;
    private List<string> messages = new List<string>();
    private string userInput;
    private string messageInput;

    protected override async Task OnInitializedAsync()
    {
        hubConnection = new HubConnectionBuilder()
            .WithUrl(NavigationManager.ToAbsoluteUri("/chathub"))
            .Build();

        hubConnection.On<string, string>("ReceiveMessage", (user, message) =>
        {
            var encodedMsg = $"{user}: {message}";
            messages.Add(encodedMsg);
            StateHasChanged();
        });

        await hubConnection.StartAsync();
    }

关于c# - List.Add 未完美添加并行和异步,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69550988/

相关文章:

c# - 如何在 wpf 的用户控件中使用命令绑定(bind)?

c# - 基于比较 c# 将元素添加到列表时使用的最佳数据结构是什么

c# - .net 2.0 中的 SSL 支持

c# - 来自 *Async 方法的 IObservable 序列

javascript - 使用 Blazor Webassembly 和 ASP.NET Core 安全文件下载

c# - App.Config 错误 "Configuration system failed to initialize"

python - 将阻塞请求的库重写为异步请求

.net - 如何为 Blazor Web Assembly 实现缓存清除

c# - Blazor 独立 WASM 无法使用 MSAL 获取访问 token

c# - 等待等待与解包()