c# - 如何同步共享的 IProgress<int>

标签 c# winforms async-await task-parallel-library iprogress

我有一个异步方法 DoStuffAsyncTask.Run 产生两个任务, 并且两个任务都使用单个 IProgress<int> 报告它们的进度目的。从用户的角度来看,只有一个操作,因此显示两个进度条(每个 Task 一个)没有任何意义。这就是为什么 IProgress<int>被共享。问题是有时 UI 会以错误的顺序接收进度通知。这是我的代码:

private async void Button1_Click(object sender, EventArgs e)
{
    TextBox1.Clear();
    var progress = new Progress<int>(x => TextBox1.AppendText($"Progress: {x}\r\n"));
    await DoStuffAsync(progress);
}

async Task DoStuffAsync(IProgress<int> progress)
{
    int totalPercentDone = 0;
    Task[] tasks = Enumerable.Range(1, 2).Select(n => Task.Run(async () =>
    {
        for (int i = 0; i < 5; i++)
        {
            await Task.Delay(100); // Simulate an I/O operation
            var localPercentDone = Interlocked.Add(ref totalPercentDone, 10);
            progress.Report(localPercentDone);
        }
    })).ToArray();
    await Task.WhenAll(tasks);
}

大多数时候通知的顺序是正确的,但有时它们不是:

Sreenshot

这会导致 ProgressBar控制(未在上面的屏幕截图中显示)笨拙地来回跳跃。

作为临时解决方案,我添加了一个 lockDoStuffAsync里面方法,包括调用 IProgress.Report 方法:

async Task DoStuffAsync(IProgress<int> progress)
{
    int totalPercentDone = 0;
    object locker = new object();
    Task[] tasks = Enumerable.Range(1, 2).Select(n => Task.Run(async () =>
    {
        for (int i = 0; i < 5; i++)
        {
            await Task.Delay(100); // Simulate an I/O operation
            lock (locker)
            {
                totalPercentDone += 10;
                progress.Report(totalPercentDone);
            };
        }
    })).ToArray();
    await Task.WhenAll(tasks);
}

虽然这解决了问题,但它让我感到焦虑,因为我在拿着 lock 的同时调用了任意代码。 . DoStuffAsync方法实际上是库的一部分,可以用任何 IProgress<int> 调用实现作为参数。这打开了死锁场景的可能性。有没有更好的方法来实现 DoStuffAsync方法,不使用 lock ,但具有关于通知排序的所需行为?

最佳答案

你的问题是你需要 totalPercentDone 的增量和对 Report 的调用是原子的。

在这里使用lock 没有任何问题。毕竟,您需要某种方法使这两个操作成为原子操作。如果你真的不想使用 lock 那么你可以使用 SemaphoireSlim:

async Task DoStuffAsync(IProgress<int> progress)
{
    int totalPercentDone = 0;
    var semaphore =  new SemaphoreSlim(1,1);

    Task[] tasks = Enumerable.Range(1, 2).Select(n => Task.Run(async () =>
    {
        for (int i = 0; i < 5; i++)
        {
            await Task.Delay(100); // Simulate an I/O operation
            await semaphore.WaitAsync();

            try
            {
                totalPercentDone += 10;
                progress.Report(totalPercentDone);
            }
            finally
            {
                semaphore.Release();
            }
        }
    })).ToArray();

    await Task.WhenAll(tasks);
}

关于c# - 如何同步共享的 IProgress<int>,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61267723/

相关文章:

c# - 在使用 .NET 驱动程序插入 MongoDB 之前验证文档

c# - 如何找到 PowerShell 静态类和方法?

winforms - 如何排除 App.config 被捆绑在 .net core 3 单文件自包含 WinForms 应用程序中的 .exe 中?

winforms - MSI注册dll-自注册被认为是有害的

c# - 通用 EventArgs 和扩展方法

javascript - 在 Vuejs 中使用 async-await 等待函数内的赋值

node.js - 调试永远无法解决 promise /异步等待

c# - 用于开发 OpenGL 3D 应用程序(不是游戏)的 C++ 与 C#

c# - 通过 Web API 将 JSON 转换为 RSS?

c# - 如何在 ASP.NET Core 中对 void 方法使用异步/等待?