我有一个带有按钮和列表框的表单。我想将两个函数的结果添加到列表框中。这两个功能可能需要花费未知的时间才能完成,我想同时执行它们。一旦其中一个函数完成了计算,我想在列表框中显示结果(在另一个函数完成之前)。目前,两个功能完成后将显示结果。我不介意函数是否自行更新列表框。
async Task<string> LongTaskAsync()
{
for(int i = 0; i < 50; i++) {
Thread.Sleep(100);
}
return "Completed long task async";
}
async Task<string> ShortTaskAsync()
{
for(int i = 0; i < 5; i++) {
Thread.Sleep(100);
}
return "Completed short task async";
}
async void BtnRunClick(object sender, EventArgs e)
{
listBox1.Items.Clear();
var longTask = Task.Run(() => LongTaskAsync());
var shortTask = Task.Run(() => ShortTaskAsync());
listBox1.Items.Add(await longTask);
listBox1.Items.Add(await shortTask);
}
答案 0 :(得分:3)
它同时显示其中两个的原因与您如何链接待命有关。
listBox1.Items.Add(await longTask);
listBox1.Items.Add(await shortTask);
您正在等待较短的任务,然后再等待较短的任务。第二行是在较长的任务完成工作之后运行的,而这又是较短的时间,这就是为什么您同时看到它们的原因。但是在现实世界中,您不知道执行什么任务将需要更长的时间,您需要一个更好的解决方案。
Action<Task<string>> continuationFunction = t => { this.listBox1.Items.Add(t.Result); };
Task.Run(() => LongTaskAsync()).ContinueWith(continuationFunction, TaskScheduler.FromCurrentSynchronizationContext());
Task.Run(() => ShortTaskAsync()).ContinueWith(continuationFunction, TaskScheduler.FromCurrentSynchronizationContext());
TaskScheduler.FromCurrentSynchronizationContext()
用于避免跨线程访问异常。
答案 1 :(得分:3)
您不必为此使用ContinueWith
。几乎总是可以避免混合async/await
和ContinueWith
风格的延续。就您而言,可以这样完成:
async void BtnRunClick(object sender, EventArgs e)
{
listBox1.Items.Clear();
async Task longTaskHelperAsync() {
// probably, Task.Run is redundant here,
// could just do: var item = await LongTaskAsync();
var item = await Task.Run(() => LongTaskAsync());
listBox1.Items.Add(item);
}
async Task shortTaskHelperAsync() {
// probably, Task.Run is redundant here, too
var item = await Task.Run(() => ShortTaskAsync());
listBox1.Items.Add(item);
}
await Task.WhenAll(longTaskHelperAsync(), shortTaskHelperAsync());
}
我相信这种方式更具可读性,您不必担心同步上下文,FromCurrentSynchronizationContext
等。
另外,如果在那些异步ctask仍在运行中的情况下再次单击BtnRunClick
,则很可能需要重新输入。
答案 2 :(得分:1)
您可以通过创建一种等待任务的方法,并将该任务的结果添加到ListBox
中,来更一般地解决该问题。
async Task ProcessAndAddToListAsync(Func<Task<string>> function)
{
var value = await Task.Run(function); // Start the task in a background thread
listBox1.Items.Add(value); // Update the control in the UI thread
}
然后在按钮单击事件的事件处理程序中使用此方法:
async void BtnRunClick(object sender, EventArgs e)
{
listBox1.Items.Clear();
var longTask = ProcessAndAddToListAsync(LongTaskAsync);
var shortTask = ProcessAndAddToListAsync(ShortTaskAsync);
await Task.WhenAll(longTask, shortTask); // optional
// Here do anything that depends on both tasks being completed
}