如何跟踪循环中完成的异步任务数量?

Rac*_*hel 5 c# multithreading task task-parallel-library c#-4.0

我有一些代码循环遍历记录列表,为每个记录启动导出任务,每次任务完成时将进度计数器增加1,以便用户知道进程的距离.

但是根据我的循环的时间,我经常看到输出在较低的数字之前显示更高的数字.

例如,我希望看到这样的输出:

Exporting A
Exporting B
Exporting C
Exporting D
Exporting E
Finished 1 / 5
Finished 2 / 5
Finished 3 / 5
Finished 4 / 5
Finished 5 / 5

但相反,我得到这样的输出

Exporting A
Exporting B
Exporting C
Exporting D
Exporting E
Finished 1 / 5
Finished 2 / 5
Finished 5 / 5
Finished 4 / 5
Finished 3 / 5

我不希望输出是准确的,因为当我更新/使用它时我没有锁定值(有时它输出相同的数字两次,或跳过一个数字),但我不希望它倒退.

我的测试数据集是72个值,相关代码如下所示:

var tasks = new List<Task>();
int counter = 0;

StatusMessage = string.Format("Exporting 0 / {0}", count);

foreach (var value in myValues)
{
    var valueParam = value;

    // Create async task, start it, and store the task in a list
    // so we can wait for all tasks to finish at the end
    tasks.Add(
        Task.Factory.StartNew(() =>
        {
            Debug.WriteLine("Exporting " + valueParam );

            System.Threading.Thread.Sleep(500);
            counter++;
            StatusMessage = string.Format("Exporting {0} / {1}", counter, count);

            Debug.WriteLine("Finished " + counter.ToString());
        })
    );
}

// Begin async task to wait for all tasks to finish and update output
Task.Factory.StartNew(() =>
{
    Task.WaitAll(tasks.ToArray());
    StatusMessage = "Finished";
});
Run Code Online (Sandbox Code Playgroud)

输出可以在调试语句和StatusMessage输出中向后显示.

什么是正确的方法来计算循环中有多少异步任务完成,以便不会发生此问题?

ale*_*lex 7

您获得混合输出,因为计数器不会按与Debug.WriteLine(...)执行方法相同的顺序递增 .

要获得一致的进度报告,您可以在任务中引入报告锁

tasks.Add(
    Task.Factory.StartNew(() =>
    {
        Debug.WriteLine("Exporting " + valueParam );

        System.Threading.Thread.Sleep(500);
        lock(progressReportLock)
        {
           counter++;
           StatusMessage = string.Format("Exporting {0} / {1}", counter, count);
           Debug.WriteLine("Finished " + counter.ToString());
        }
    })
);
Run Code Online (Sandbox Code Playgroud)


Jar*_*Par 5

在此示例中,counter变量表示多个线程之间的共享状态.++在共享状态下使用运算符根本不安全,并且会给您不正确的结果.它基本上归结为以下说明

  1. 将计数器推到堆栈
  2. 按1进行堆叠
  3. 在堆栈上添加值
  4. 存入柜台

因为多个线程正在执行此语句,所以可以通过完成上述序列来中断其他线程.这将导致不正确的值结束counter.

而不是++使用以下语句

Interlocked.Increment(ref counter);
Run Code Online (Sandbox Code Playgroud)

此操作专门用于更新可在多个线程之间共享的状态.互锁将以原子方式发生,不会受到我概述的竞争条件的影响

即使在我建议的修复之后,实际的无序显示值也会遇到类似的问题.增量和显示操作不是原子操作,因此一个线程可以在增量和显示之间中断另一个线程.如果您希望操作不被其他线程中断,那么您将需要使用锁.

object lockTarget = new object();
int counter = 0; 

...

lock (lockTarget) {
  counter++;
  StatusMessage = string.Format("Exporting {0} / {1}", counter, count);
  Debug.WriteLine("Finished " + counter.ToString());
}
Run Code Online (Sandbox Code Playgroud)

请注意,因为counter现在增量发生在锁内部,所以不再需要使用Interlocked.Increment

  • 看起来像使用Interlocked.Increment对她得到的无序状态报告没有帮助. (2认同)