关于异步任务

时间:2019-11-16 10:47:43

标签: c# winforms asynchronous async-await task

我有一个带有按钮和列表框的表单。我想将两个函数的结果添加到列表框中。这两个功能可能需要花费未知的时间才能完成,我想同时执行它们。一旦其中一个函数完成了计算,我想在列表框中显示结果(在另一个函数完成之前)。目前,两个功能完成后将显示结果。我不介意函数是否自行更新列表框。

    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);        
    }

3 个答案:

答案 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/awaitContinueWith风格的延续。就您而言,可以这样完成:

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
}