【问题标题】:Async/await vs Task.Run in C#C# 中的 Async/await 与 Task.Run
【发布时间】:2016-04-13 18:22:15
【问题描述】:

我只是这个异步事物世界的新手。
请原谅我缺乏知识。

  1. 据说当一个方法遇到await ... "它告诉awaitable在完成时运行该方法的剩余部分,然后从async方法返回。" 我没有得到这部分。
    那么这是否意味着该方法仍然保持同步运行并等待直到可等待返回然后继续该方法的其余部分?
    如果不是,请解释为什么需要Task.Run 在后台或以一劳永逸的方式 运行方法。我仍然可以通过 await 实现对吗?即
    该方法会继续执行其余的语句,而无需等待 await 返回。
    我希望这类似于后台运行方法。或者不是吗?我很困惑。

  2. 如果一个方法被标记为 async 和 await,而该方法又在一个单独的层中异步调用另一个方法,该层也被标记为 async 和 await ..
    那么从单独的方法中调用标记为 async 和 await 的第一个方法的调用名称 ABC 应该是什么样子?
    我不想将该方法注释为异步/等待。所以

    Task.Run(() => DoWork());
    

    来自 ABC() 没有标记为 async/await 就可以了吗?
    还是违背了异步原则?

这就是我想要实现的目标......

   public IList<CreateCaseOutput> ABC(CreateCaseInput CreateCaseInput,SaveCaseSearchInput SaveCaseSearchInput)
    {
        CaseSQL.getABCParameters(CreateCaseInput, RequestType, out strSPQuery, out listParam);
        var AcctLst = rep.ExecuteStoredProcedure<CreateCaseOutput>(strSPQuery, listParam).ToList();

        if (!string.IsNullOrEmpty(AcctLst.ElementAt(0).o_case_seq.ToString()))
        {
            Task.Run(async () =>
            {
                await DEF(SaveCaseSearchInput, AcctLst.ElementAt(0).o_case_seq);
            }).ConfigureAwait(false);              
        }
        console.writeLine("After Async called");
        return AcctLst;
    }

    public async Task<SaveCaseSearchOutput>> DEF(SaveCaseSearchInput SaveCaseSearchInput,Int64? case_key)
    {

            CaseSQL.getDEFParameters(SaveCaseSearchInput, case_key, out strSPQuery, out listParam);
            var AcctLst = await rep.ExecuteStoredProcedureAsync<SaveCaseSearchOutput>(strSPQuery, listParam);
            return AcctLst;
    }

需要在后台调用 async/await 的 DEF 并忘记来自 ABC 的方法,一旦被触发,我想继续使用 ABC 的其余部分并在后台运行 DEF。这种火灾和忘记方法有什么问题?如果我打电话给

只有

DEF(SaveCaseSearchInput, AcctLst.ElementAt(0).o_case_seq); 

而不是

 Task.Run(async () =>
                {
                    await DEF(SaveCaseSearchInput, AcctLst.ElementAt(0).o_case_seq);
                }).ConfigureAwait(false);              
            }
            return AcctLst;

然后这段代码在调试器中同步运行。

我做错了吗?

【问题讨论】:

  • 我想解释一下,但这里已经很简单了,看看这是否有帮助stephenhaunts.com/2014/10/10/…
  • 一个很好的解释是here也一定要检查斯蒂芬的introductory article
  • 我使用 Run 来将同步执行放在一个任务中(在任务线程池上运行)。即var task = Task.Run(() =&gt; DoWork()); /** do something else **/ await task;

标签: c# .net multithreading asynchronous


【解决方案1】:

这是否意味着该方法仍然保持同步运行并等待直到可等待返回然后继续该方法的其余部分?

没有。 awaitable 有一个“回调”机制。因此,该方法只是将自身注册到可等待对象。当 awaitable 完成时,它将执行其回调,其中包括 async 方法的延续。

与此同时,async 方法返回一个未完成的任务。

如果不是,请解释为什么需要 Task.Run 来在后台或以火而忘记的方式运行方法。

如果您想在后台线程上运行,请使用Task.RunTask.Run 只是将工作安排到线程池中。

我仍然可以通过 await 实现对吗? IE。 该方法继续执行其余的语句,而无需等待 await 返回。 我希望这类似于后台运行方法。或者不是吗?我很困惑。

好吧,你可以通过等待它来“忘记”任务:

MyMethod()
{
  SomeMethodAsync(); // Note: no await; we just ignore the task.
}

不过,您几乎从不想要进行“一劳永逸”。很容易超过 90% 的时间人们要求它,这实际上是一个设计错误。

我不想将该方法注释为异步/等待...还是违反异步原则?

这是违反原则的。如果您考虑一下,阻塞异步代码实际上没有任何意义。你想经历使方法异步的麻烦(意味着它不会阻塞线程),然后阻塞它上面的线程吗?为什么,究竟是什么?有时在实践中,代码会暂时在过渡到异步代码期间进入这种状态,但这是一个棘手的地方。

有关更多信息,请参阅我在 async best practices 上的文章,尤其是“一路异步”。

【讨论】:

  • 请参阅更新后的详细信息中附加的示例。为什么在这种情况下调用 DEF 并忘记方法是错误的。我想继续使用 ABC(如果有其他东西从 ABC 返回),同时继续使用 DEF。但我不想标记 ABC async / await ?我们试图实现的目标有问题吗?
  • 让我扭转局面。您一直要求同步而不是异步和即发即弃,这两种模式都绝对是反模式。 为什么您“不想将 ABC 标记为异步”?
  • 其实在我的web api代码中..代码分为多层。该场景类似于First we create a case and we get the case key in return after completion of the DB Call. 这是以同步方式实现的。现在,如果成功返回 case 键,我有一些额外的输入可以将 case 添加到 DB 中,我们不必等待它们被存储并返回结果。我想我们可以触发savecase call to DB,然后继续将生成的 case 键一直返回到 View。所以我没有标记createcase(here ABC) async
  • @RaulAgarwal:哦,这是在 WebAPI 项目中吗?在这种情况下,几乎可以肯定,即发即弃是您不想想要做的事情。例如,您是否知道一旦您从控制器返回结果,ASP.NET 就可以关闭您的 AppDomain?您的数据库保存将丢失。我列出了a few workarounds on my blog,但底线是 ASP.NET 并非设计为在请求的外部运行代码。
  • 您不应在 ASP.NET 上使用 Task.Run。如果您完全使用async,则一直使用它直到控制器。这就是它应该的方式。如果客户端想要在请求进行时做其他工作,那么客户端可以使用异步编程。
【解决方案2】:

'据说当一个方法遇到await时...“它告诉awaitable在完成时运行该方法的剩余部分,然后从async方法返回。”'

假设您有一个调用方法 B 的方法 A。方法 B 在其声明中具有“async”关键字。在方法 B 中有一个耗时的 LongMethod 调用,最终返回一个字符串。

我们在这里想要的是方法 A 不必等待方法 B 的 LongMethod 完成的时间。所以我们写在方法B:

string newVariable = await Task.Run(()=&gt;LongMethod());

如果 LongMethod 本身也是异步的,那么 LongMethod 只能返回一个 void、一个非泛型 Task 或一个泛型 Task。因此,我们必须将 LongMethod 声明中的返回类型更改为 Task&lt;string&gt;

那么回到我们的故事,LongMethod 任务已经开始了……此时方法 A 从调用方法 B 后的下一行代码继续,方法 B 继续等待 LongMethod 完成,所以有现在有两个线程在运行。最终,LongMethod 完成,方法 B 运行到最后,同时方法 A 继续运行,甚至可能已经运行完毕。

我希望这能让您了解 Async/Await 和 Tasks 之间的关系。

现在回答问题 2:

我认为您的问题基本上是在询问我的示例中的方法 A 是否需要标记为“异步”,因为它调用了标记为“异步”的方法,不,它不需要。另请注意 ABC() 只需要有

DoWork() 不是 Task.Run(() =&gt; DoWork());

ABC 将停止运行,直到 DoWork 方法到达 'await' 语句。

如果 DoWork 只需要作为自己的任务运行,并且您根本不希望 ABC() 停止,那么就这样做

Task.Run(() =&gt; DoWork());

在这种情况下,将 DoWork 标记为异步没有任何好处,因为在 DoWork 中没有任何等待语句。

希望对您有所帮助,如果我误解了您的问题,请见谅!

【讨论】:

  • 请参阅更新后的详细信息中的示例。为什么在这种情况下调用 DEF 并忘记方法是错误的。我想继续使用 ABC(如果有其他东西从 ABC 返回),同时继续使用 DEF。但我不想标记 ABC async / await ?我们试图实现的目标是否有问题。
  • 您还说“在这种情况下将 DoWork 标记为异步没有任何好处,因为在 DoWork 中没有任何等待语句。” ...但 DEF 又等待另一个长时间运行的操作 ExecuteStoredProcedureAsync .. 那么我做错了什么?
  • 你为什么不能做Task.Run(() =&gt; DEF(SaveCaseSearchInput, AcctLst.ElementAt(0).o_case_seq))
  • 如果您绝对确定要让 DEF 以“一劳永逸”的方式运行,那为什么不能只执行 Task.Run(() =&gt; DEF(SaveCaseSearchInput, AcctLst.ElementAt(0).o_case_seq))?但是,您的 DEF 方法返回 AcctLst,它当前在 ABC 中没有指向它的引用。您确定要让它成为火而忘记任务吗?
  • 我想在后台运行它,而我希望主 ABC() 继续执行该方法的其余部分。当后台任务完成时,我可能希望也可能不希望返回结果。如果我需要返回结果并且还想在后台运行它,将 async/await 帮助或 task.run 吗?怎么做?你能展示一下吗?我认为在第一个示例中您要求使用await Task.Run(()=&gt;LongMethod()); 对吗?
【解决方案3】:

根据您在评论中所说的(我假设您需要 ABC 中的结果)

你只需要写

Task&lt;SaveCaseSearchOutput&gt; t = Task.Run(() =&gt;DEF(SaveCaseSearchInput,AcctLst.ElementAt(0).o_case_seq));

在 ABC 中,而不是当前的 Task.Run 调用。

然后在 ABC 的行当你需要结果时写入

t.Wait();
SaveCaseSearchOutput g = t.Result;

DEF 必须保持异步,但您可以删除 DEF 中的 await 语句。

【讨论】:

  • 不,我猜实际上 await 必须在 DEF 方法主体中,因为它正在等待 rep.ExecuteStoredProcedureAsync 完成。还有吨。 Wait() 不会阻塞主线程吗?还是会阻塞调用任务的后台线程?而 await Task.Run() 和 Task.Run(()=> ......) 有什么区别?
  • 如果你需要ABC任务的结果,你必须阻塞ABC的线程等待结果,别无选择。我刚刚向您展示了如何最小化阻塞。在等待 rep.ExecuteStoredProcedure... 完成时,您希望发生什么?您希望呼叫者继续运行吗?已经是这样了,因为 DEF 方法在它自己的任务中运行,所以你不需要写“等待”。 Task.Run 总是有一个参数。
  • ...我明白,先生。但是 ExecuteStoredProcedureAsync 对 DB 有一个 async/await 调用。所以它被标记为异步/等待。因此调用者也是。这就是为什么 DEF 被标记为 async/await 的原因。如果我从 DEF 的声明中删除 async/await .....即使 DEF 在后台线程上运行并最终也阻塞它,难道不是所有的同步调用吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-08-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-16
  • 1970-01-01
  • 2022-01-07
相关资源
最近更新 更多