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输出中向后显示.
什么是正确的方法来计算循环中有多少异步任务完成,以便不会发生此问题?
您获得混合输出,因为计数器不会按与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)
在此示例中,counter变量表示多个线程之间的共享状态.++在共享状态下使用运算符根本不安全,并且会给您不正确的结果.它基本上归结为以下说明
因为多个线程正在执行此语句,所以可以通过完成上述序列来中断其他线程.这将导致不正确的值结束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
| 归档时间: |
|
| 查看次数: |
2907 次 |
| 最近记录: |