我有一些代码循环遍历记录列表,为每个记录启动一个导出任务,每次任务完成时将进度计数器增加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";
});
输出可以在调试语句和StatusMessage
输出中向后显示。
在循环中完成多少异步任务的计数的正确方法是什么,以便不会出现此问题?
答案 0 :(得分: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());
}
})
);
答案 1 :(得分:5)
在此示例中,counter
变量表示多个线程之间的共享状态。在共享状态下使用++
运算符根本不安全,会给您不正确的结果。它基本上归结为以下说明
由于多个线程正在执行此语句,因此可以通过完成上述序列来中断其他线程。这会导致不正确的值以counter
结尾。
而不是++
使用以下语句
Interlocked.Increment(ref counter);
此操作专门用于更新可在多个线程之间共享的状态。互锁将以原子方式发生,不会受到我概述的竞争条件的影响
即使在我建议的修复之后,实际的无序显示值也会遇到类似的问题。增量和显示操作不是原子操作,因此一个线程可以在增量和显示之间中断另一个线程。如果您希望操作不被其他线程中断,那么您将需要使用锁。
object lockTarget = new object();
int counter = 0;
...
lock (lockTarget) {
counter++;
StatusMessage = string.Format("Exporting {0} / {1}", counter, count);
Debug.WriteLine("Finished " + counter.ToString());
}
请注意,因为现在counter
的增量发生在锁内,所以不再需要使用Interlocked.Increment