【问题标题】:About asynchronous tasks关于异步任务
【发布时间】:2020-03-12 08:47:22
【问题描述】:

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

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

【问题讨论】:

  • 不要在任务中使用Thread.Sleep。使用Task.Delayawait
  • 感谢您的建议,我以后会这样做。对于这个例子,我认为这并不重要。
  • 是的,我知道它不能解决任何关于这个问题的问题,但我只是说:)
  • 我现在没时间回答,看ContinueWith,我想你可以解决它。

标签: c# winforms asynchronous async-await task


【解决方案1】:

它同时显示其中 2 个的原因与您如何链接等待有关。

 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()是为了避免跨线程访问异常。

【讨论】:

    【解决方案2】:

    您不必为此使用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,您很可能需要处理可能的重新进入。

    【讨论】:

    • 感谢 Noseratio。它可以工作,而且看起来比其他代码更容易理解。我认为您错误地将两个异步函数放在 BtnRunClick 函数中。
    • @Nick_F,您的目标是 C# 7.0+,它支持 local functions
    • @Nick_F,那么您仍然可以将异步 lambdas 用于本地函数:Func&lt;Task&gt; longTaskHelperAsync = async () =&gt; { ... };
    • 是的,我把两个本地函数移到外面,运行正常。
    • @Nick_F,是的,如果可以的话,最好将它们移出。我使用局部函数的唯一原因是为了表明您可以使用 ContinueWith lambda 完成几乎所有可以做的事情,包括访问局部变量。
    【解决方案3】:

    您可以通过创建一个等待任务的方法更通用地解决它,并将任务的结果添加到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
    }
    

    【讨论】:

    • 谢谢 Theodor,这个解决方案更好——只需要一个额外的功能。
    • 看来我可以将 LongTaskAsync() 和 ShortTaskAsync() 函数的返回类型更改为字符串,但我仍然得到了想要的结果。它们应该重命名为 LongTask() 和 ShortTask()。原始函数,我创建它们的方式无论如何都缺少 await 运算符。
    • @Nick_F 是的,Task.Run 是一种通用方法,可以很好地处理同步和异步委托。
    • Theodor,如果我的函数有参数,我如何从 Task.Run 调用它们?
    • @Nick_F 你可以使用 lambdas 来传递临时参数:ProcessAndAddToListAsync(() =&gt; LongTaskAsync(arg1, arg2))
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-01
    • 1970-01-01
    • 2023-03-11
    • 1970-01-01
    • 2012-09-27
    相关资源
    最近更新 更多