我有一个异步方法DoStuffAsync
,它使用 生成两个任务Task.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);
}
大多数情况下,通知的顺序是正确的,但有时它们不是:
这会导致ProgressBar
控件(上面的屏幕截图中未显示)笨拙地来回跳跃。
作为临时解决方案,我在方法lock
内部添加了一个DoStuffAsync
,其中包括方法的调用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
方法,而不使用 a lock
,但具有关于通知排序的所需行为?