【问题标题】:Should I be using await inside my Task.Run()?我应该在我的 Task.Run() 中使用 await 吗?
【发布时间】:2020-04-16 13:54:02
【问题描述】:

** 我在底部总结了这个问题,并进行了编辑**

以前有人问过这个问题,但我认为我的情况不同。

我正在同时处理多个请求。

这是我的代码,它在循环中运行。为简洁起见,我删除了一些处理 taskAllocated 变量的代码。

 while (!taskAllocated)
 {
     lock (_lock)
     {
         // Find an empty slot in the task queue to insert this task
         for (i = 0; i < MaxNumTasks; i++)
         {
             if (_taskQueue[i] == null)
             {
                 _taskQueue[i] = Task.Run(() => Process());
                 _taskQueue[i].ContinueWith(ProcessCompleted);
                 break;
             }
         }
     }
  }

Process是一个典型的async Task Process() { CpuIntensiveStuff(); }方法。

我一直在运行上面的代码,并且运行良好。它很好地多线程。每当有一个项目进来时,它就会在任务队列中找到一个空槽,然后将其启动。当任务完成时,ProcessCompleted 方法运行,并释放槽。

但后来我想,我不应该在我的Task.Run 中使用await 吗?比如:

_taskQueue[i] = Task.Run(async () => await Process());

想了想,我也不确定。 ContinueWith 在任务完成后正确触发,因此可能没有必要。

我问是因为我想监控和记录每项任务需要多长时间才能完成。

因此,我将创建另一种方法,而不是 Process():

async Task DoProcess() 
{ 
    var sw = Stopwatch.StartNew();
    Process();
    sw.Stop();
    Log(sw.ElapsedMilliseconds);
}

我突然想到,如果我这样做了,我不确定是否需要await Process();,除了不知道我是否应该在Task.Run() 中等待

我对此有点紧张。谁能提供指导?


编辑:

总结一下:

如果某个方法是:

void SomeMethod() { }

然后

Task.Run(() =&gt; SomeMethod()); 很棒,在一个新的“线程”上调用 SomeMethod(技术上不是,但你知道我的意思)。

然而,我的SomeMethod其实是:

async Task SomeMethod() { }

您需要对Task.Run() 做一些特别的事情吗?

我的代码,我不是,我只是直接忽略了它是一个异步任务,而且这似乎有效:

Task.Run(() =&gt; SomeMethod()); // SomeMethod is async Task but I am ignoring that

但我不相信它 a) 应该有效或 b) 是一个好主意。替代方法可能是:

Task.Run(async() =&gt; await SomeMethod());

但是有什么意义吗?而这与我真正想做的事实相得益彰:

Task.Run(() => 
{ 
    someCode(); 
    var x = startTimer();
    SomeMethod(); 
    var y = stopTimer();
    someMoreCode()
}); 

但如果没有等待,我不确定它是否会等待某个方法完成并且计时器会出错。

【问题讨论】:

  • 不幸的是,并非如此。他们说An async method returns to the caller as soon as the first await is hit,但这只是在等待的情况下,不是吗?如果你不等待它,它只会返回一个任务并继续运行。
  • @PavelAnikhouski 这个例子不太一样,因为它使用await 与.Result 进行比较。
  • 这个问题真的是和this重复的。匿名方法只是掩盖了这一点。这只是直接返回Task 或return await 之间的区别。如果你用完整的方法(不是匿名方法)写出来,就会变得清晰。
  • 您说ProcessCompleted 工作正常,这令人惊讶,因为您似乎创建了Task&lt;Task&gt; 对象,这通常是一种糟糕的结束方式。如果我们有一个小的完整样本,我们可以自己运行和观察,这会有所帮助。
  • Task.Run 可以also accept a Func&lt;Task&gt;。从您的方法推断正在使用哪个重载。如果something() 返回Task,则使用Func&lt;Task&gt; 重载。但是,返回 Task 的方法存在特定重载这一事实让我想知道我的答案是否真的正确:) 我想测试一下......

标签: c# async-await task


【解决方案1】:

如果您不使用匿名方法,事情就会变得更加清晰。例如,

Task.Run(() => Process())

等价于:

Task.Run(DoSomething);

Task DoSomething() {
    return Process();
}

而

Task.Run(async () => await Process())

等价于:

Task.Run(DoSomething);

async Task DoSomething() {
    await Process();
}

在大多数情况下,return SomethingThatReturnsATask() 和return await SomethingThatReturnsATask() 之间没有功能上的区别,您通常希望直接返回Task 而不要使用await(原因描述为here)。在 Task.Run 内部使用时,如果 .NET 团队没有您的支持,事情很容易变糟。

请务必注意,异步方法开始在同一个线程上运行,就像任何其他方法一样。魔法发生在第一个await,它作用于不完整的Task。此时,await返回它自己不完整的Task。这很重要 - 它返回,并承诺稍后再做。

这可能意味着只要Process()返回Task,从Task.Run返回的Task就会完成。由于Process() 在第一个await 处返回Task,这将在尚未完全完成时发生。

.NET 团队支持您

但情况并非如此,因为Task.Run 有一个specific overload for when you give it a method returning a Task。如果你look at the code,它会返回一个Task *,它与你返回的Task 相关联。

这意味着从Task.Run(() =&gt; Process()) 返回的Task 在从Process() 返回的Task 完成之前不会完成。

所以你的代码是好的。

【讨论】:

  • 这是有道理的。我想知道为什么我的代码可以工作。可能ContinueWith 与它有关。我在 4 小时内用超过 10 万个队列项目敲定了我的代码,并且没有产生任何错误或问题。我将更新它以使用异步等待,然后我可以插入我的计时器代码来测量执行需要多长时间。非常感谢你。我总是在异步等待中挣扎。
  • 这完全取决于Process() 的写法。在第一个 await 之前,它可能已经完成了您需要它完成的大部分工作,这就是您从未见过任何错误的原因。
  • 天哪,我想你可能是对的。它从队列中删除任务,但这并不意味着该任务仍在运行。非常感谢您!
  • 哦,但我的代码没有返回Task 是吗? () =&gt; Process() 是 async Task,因此返回类型为 void。因此Task.Run(() =&gt; Process()); 而不仅仅是Task.Run(Process),如果进程被标记为async,则后者将不起作用
  • Intellisense 可以在这里为您提供帮助。将鼠标放在Task.Run(() =&gt; Process()) 中的Run 上,它会显示它正在使用哪个重载。它会告诉你它正在使用Func&lt;Task&gt; 重载。
猜你喜欢
  • 2021-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-26
  • 2023-03-06
  • 2014-08-09
相关资源
最近更新 更多