使用async而不是Task.Run()

19 浏览
0 Comments

使用async而不是Task.Run()

我有以下的代码段:\n

private void btnAction_Click(object sender, RoutedEventArgs e)
{
    /** 清空结果字段 */
    txtResult.Text = "";
    /** 禁用按钮并显示等待状态 */
    btnAction.IsEnabled = false;
    lblStatus.Text = "等待...";
    /** 从查询字段获取输入 */
    string input = query.Text;
    /** 运行一个新的任务 */
    Task.Run(() => {
        // 调用一个需要较长时间(>3秒)才能完成并返回的方法
        var attempt = someLibrary.doSomethingWith(input);
        // 将结果返回给GUI线程
        this.Dispatcher.Invoke(() =>
        {
            if (attempt.ContainsKey("success"))
            {
                if (attempt["success"] == true)
                {
                    txtResult.Text = "成功!门是:" + (attempt["is_open"] ? "打开的" : "关闭的");
                    lblStatus.Text = "";
                }
                else
                {
                    lblStatus.Text = "错误!服务返回:" + attempt["errorMessage"];
                }
            }
            else
            {
                MessageBox.Show("从网络服务获取结果时出现问题。");
                lblStatus.Text = "";
            }
            /** 启用按钮 */
            btnAction.IsEnabled = true;
        });
    });
}

\n现在,我想要:\n

    \n

  • 使用回调函数而不是使用Dispatcher.Invoke()来编写相同的代码。
  • \n

  • 能够取消调用doSomething()的运行中任务。
  • \n

  • 能够链接多个调用,即等待doSomething()完成后,使用之前调用的结果调用doAnotherThing()
  • \n

\n因此,我想要使用异步模型来编写代码。\n我该怎么做?

0
0 Comments

问题的原因是使用async修饰符要求函数返回Task<T>(或void,在这种情况下,任何await语句将被忽略)。这意味着使用async和使用Task.Run()是一样的,你的问题的前提是没有意义的。

然而,我认为你想要做的只是使用async await语法来避免显式调用Task.Run()

问题的解决方法可以按如下方式进行整理:

回调

只需创建一个返回Task的函数

async Task<T> Foo()

然后分配一个变量var bar=await Foo();

取消正在运行的任务

只需使用CancellationToken

CancellationTokenSource tokenSource = new CancellationTokenSource();

如果你使用两个参数构造一个任务,第二个参数是一个取消令牌:

var bar= new Task(action, tokenSource.Token)

这样可以使用

tokenSource.Cancel();

相关链接:https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken?view=netframework-4.8

链接调用

如果你不需要定义执行顺序,可以使用Task.WhenAll(),否则可以在前一个任务中执行下一个任务或在等待的代码中执行。

Task.WhenAll()https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.whenall?view=netframework-4.8#System_Threading_Tasks_Task_WhenAll_System_Collections_Generic_IEnumerable_System_Threading_Tasks_Task__

感谢你指引我正确的方向。那么,在这种情况下,调用tokenSource.Cancel()会取消bar吗?然后会发生什么,会抛出异常吗?还有,在这个上下文中,我确实需要链接调用,因为第一个调用的结果是第二个调用的参数。所以我不能同时异步调用这两个方法。

0
0 Comments

使用async而不是Task.Run()的原因是为了在UI线程上运行continuations,同时只将长时间运行(看起来是CPU密集型)的任务留在其中。

问题的解决方法是将方法标记为async,使用await等待Task.Run,以便continuations在UI上运行,同时在其中只留下长时间运行(看起来是CPU密集型)的任务。

要取消任务,可以使用来自CancellationTokenSource实例的CancellationToken,并将其传递给Task.Run和长时间运行的方法,以检查IsCancellationRequested(如果可以的话)。可以通过调用CancellationTokenSource.Cancel来取消。

完美。有两件事我想要补充:1. 使用Task.Factory.StartNew而不是Task.Runawait Task.Factory.StartNew(...); UpdateUi(...); await Task.Factory.StartNew(...); UpdateUi(...); ...2. 禁用按钮是第一行!一定要!相信我,如果不这样做,我可以双击。

我们为什么要使用StartNew

这是完美的,因为它消除了不必要的对Dispatcher.Invoke()的调用!它还解决了链接调用的问题,因为现在我可以await Task.Run()。为了完整起见,有没有办法取消正在运行的任务(可能会抛出异常)?

看这个

好的,将它从线程池中移除,我个人认为除非需要,否则不必担心,不过还是感谢这个提醒。

1. 研究一下Task.Factory.StartNew(),不确定有什么区别,另外2. 哇,这很有意思!我猜这是有道理的。从现在开始,我会确保始终在第一行将按钮禁用

这很棒-不过由于我无法在doSomethingWith中检查IsCancellationRequested,因为它由someLibrary处理。有没有办法强制停止任务并引发异常?

你可以使用StartNew,如果你想要将长时间运行的任务从线程池中移除,它是一种更旧的方法,具有更多细粒度的提示来控制其行为,我个人现在只会使用更新的Task.Run,除非你在使用线程池资源时遇到问题。要取消任务,你将令牌传递给Task.Run,但一定要捕获异常。

是的,应该声明一个CancellationTokenSource,然后使用它的.Token。我不会让任务处理它,而是直接传递给doSomethingWith()作为第二个参数。在内部,我会写token.ThrowIfCancellationRequested();(在循环中特别需要进行检查)。还有,既然我们已经提到了:你应该知道所有的await都应该用try-finally(或try-catch)包裹,因为它们不会上升到最顶层的调用者!示例:try { await Task.Run(async () => await Task.Run(() => throw new Exception())); } catch {} 在发布模式下不会被捕获并会导致应用程序崩溃。

如果你可以访问SomeLibrary,你应该重构代码,添加第二个CancellationToken参数,因为只要不需要它,你只需传入CancellationToken.None

我知道有一点,就是没有为任务实现Task.Abort,可能是因为不可靠的Thread.Abort无法依赖(不是Abort本身,而是你在该线程上执行的内容)。

你可以创建第二个(虚假的)任务(只在取消时返回),作为主任务的对手,并使用Task.WhenAny()。但这只能让你在主线程上继续执行,而不能停止另一个任务。只有在关闭应用程序(线程已经关闭)或者你可以忘记一个网络调用的情况下才能接受。因为:1.:你可能允许用户重新启动仍在后台运行的任务。2.:通过丢失try { await ...; } catch {}来失去鲁棒性。

Task.Run对于这种情况优于StartNew

0