【问题标题】:is await TaskRun(() => PublicVoidNotAsycMethod) equivalent to PublicVoidNotAsycMethod()是 await TaskRun(() => PublicVoidNotAsycMethod) 等价于 PublicVoidNotAsycMethod()
【发布时间】:2016-02-09 08:58:13
【问题描述】:

我有一个async 方法:

public async void BillSubscriptions()
{
   await Task.Run(() => ProcessSubscriptions(_subscriptionRepository));
   await Task.Run(() => ProcessNonRecurringSubscriptions(_subscriptionRepository));
   await Task.Run(() => ProcessTrialSubscriptions(_subscriptionRepository));
}

请注意,ProcessSubscriptionsProcessNonRecurringSUbscriptionsProcessTrialSUbscriptions 是私有 void 方法,而不是异步方法。

所有这些方法都从数据库中检索数据并根据一些算法对其进行处理和更新数据库。

我的问题是,上面的代码是否等同于下面的代码?

public async void BillSubscriptions()
{
   ProcessSubscriptions(_subscriptionRepository);
   ProcessNonRecurringSubscriptions(_subscriptionRepository);
   ProcessTrialSubscriptions(_subscriptionRepository);
}

【问题讨论】:

  • 在什么方面等效?
  • 我的意思是下面的代码是在 BillSubscriptions 方法的同一线程中运行的正常同步调用。我的问题是上面的调用也是在不同线程中运行的异步调用但是因为等待是同步的?
  • 另一个问题,为什么我要使用上面的代码而不是下面的代码,使用上面的代码对我有什么好处?
  • @NadeemTabbaa:我认为核心误解是围绕async 和线程。简单地说,async在后台线程上运行代码

标签: c# .net multithreading asynchronous async-await


【解决方案1】:

它们完全不同

在您的第一个示例中,您有以下内容:

public async void BillSubscriptions()
{
   await Task.Run(() => ProcessSubscriptions(_subscriptionRepository));
   await Task.Run(() => ProcessNonRecurringSubscriptions(_subscriptionRepository));
   await Task.Run(() => ProcessTrialSubscriptions(_subscriptionRepository));
}

一个async void方法,即BillSubscriptions。这个方法是public,因此任何人都可以调用它。它在内部以特定顺序等待三个private 方法。这些方法被指示通过 Task.Run 函数运行,在这种情况下,该函数接受解析为 Action 委托的 lambda 表达式。这些方法按顺序执行并排队运行在 ThreadPool 线程上,返回一个 Task 对象来表示异步操作。

有关其功能的详细信息,请参阅Task.Runhere

其他操作如下:

public async void BillSubscriptions()
{
   ProcessSubscriptions(_subscriptionRepository);
   ProcessNonRecurringSubscriptions(_subscriptionRepository);
   ProcessTrialSubscriptions(_subscriptionRepository);
}

我们再次有一个标记为asyncpublic 方法名为BillSubscriptions,它执行三个按顺序执行的private 方法。不同的是,这些都是在当前线程上运行并且是阻塞的。而在前面的示例中,代码不会阻塞,它们可能(很可能)在不同的线程上执行。我做了一些修改来展示这些差异:

Here is the link for the .NET fiddle that will hopefully make this more clear.

这是输出:

Is async = True, ProcessSubscriptions :: Thread ID10
Is async = True, ProcessNonRecurringSubscriptions :: Thread ID11
Is async = True, ProcessTrialSubscriptions :: Thread ID10
Is async = False, ProcessSubscriptions :: Thread ID9
Is async = False, ProcessNonRecurringSubscriptions :: Thread ID9
Is async = False, ProcessTrialSubscriptions :: Thread ID9

注意:

  1. 避免使用async void,因为它会破坏async 状态机
  2. 由于类的方法是private,所以改为使用async。将它们重命名为以MethodNameAsync 为后缀,使它们返回Task,并在它们的体内让它们返回Task.Run(() => { ... });

既然您似乎想了解拥有async 代码是否有优势,那么……事实上非常如此。由于这三个private 方法不需要等待另一个的返回值,它们都可以并行运行。您可以使用Task.WhenAll 来获得显着的性能提升。例如,如果每个方法的执行时间接近 1 秒,那么它们至少需要 3 秒才能同步运行,但是,如果并行执行 - 只需最长执行时间三者中的一个。

【讨论】:

  • 非常有趣的信息谢谢,我知道了,它有一个清晰而详细的信息,是的,你已经回答了我的问题
【解决方案2】:

如果您的方法正在执行数据库调用,那么您应该一直使用async awaitToListAsync()db.SaveChangesAsync(),如果您使用的是 EF)并且不要将它们包装在 Task.Run() 中。您想要这样做的原因是线程在等待 I/O-Bound 操作(在这种情况下为 DB 调用)完成时不会被阻塞,并且可以被分配做其他事情(例如处理另一个 HTTP 请求以防万一网络应用程序)。

【讨论】:

  • 我理解您的回答,如果我希望 IO 操作异步,这是合理的,但不管怎样,await Task.Run() 是否表现为直接调用该方法?唯一的区别是 await Task.Run 现在是在不同的线程中运行虚假的异步调用,但实际上它就像正常的同步调用一样?
  • 或者你可以说,我为什么要使用上面的代码,对我有什么好处?
  • "await Task.Run() 是否表现得像直接调用该方法??"不,直接调用方法会阻塞调用线程,直到方法完成。如果你等待任务,那么线程不会被阻塞,它可以做其他事情,当任务完成时你的代码将继续执行。
【解决方案3】:

有区别。

在您的第一个示例中,您在不同的线程上同时执行 Process 方法并依次等待每个线程。
根据您的执行上下文(例如 ASP.NET 或 XAML 应用程序),BillSubscriptions 方法可能会继续在主线程上运行(因为您没有使用 ConfigureAwait(false)),或者它可能会在某些已执行的线程池线程上继续运行Process 方法。

在第二个示例中,您只是同步执行。进行的调度和任务/上下文切换较少。
除非我弄错了,否则这个版本不起作用,因为你不是 awaiting 任何东西,所以它不能是 async

在这种情况下,单独的 async 关键字无论如何都不会为您做任何事情,因为 async 方法同步运行,直到它们到达第一个 await

如果您想在不阻塞的情况下执行所有 3 个操作,您有 3 个选项:

  1. 使Process 方法异步并等待它们(不带Task.Run
  2. 使用不带async 关键字的第二个版本,并以Task.Run(() => BillSubscriptions()) 开头
  3. 如果Process 方法不必按顺序执行并且可以同时运行,您可以将您的第一个版本修改为:

.

public async void BillSubscriptions()
{
   await Task.WhenAll(
      Task.Run(() => ProcessSubscriptions(_subscriptionRepository));
      Task.Run(() => ProcessNonRecurringSubscriptions(_subscriptionRepository));
      Task.Run(() => ProcessTrialSubscriptions(_subscriptionRepository))
   );
}

【讨论】:

  • 非常有趣的信息谢谢,我知道了,它有一个清晰而详细的信息,是的,你已经回答了我的问题。
【解决方案4】:

说实话,不是。但如果你只关心执行顺序,BillSubscriptions#2 等价于BillSubscriptions#1。那是因为等待后的代码被添加到Task.ContinueWith method

没有async/await BillSubscriptions 看起来像:

public void BillSubscriptions()
{
    Task t1 = Task.Run(() => ProcessSubscriptions(_subscriptionRepository));
    t1.ContinueWith(t =>
    {
        Task t2 = Task.Run(() => ProcessNonRecurringSubscriptions(_subscriptionRepository));
        t2.ContinueWith(tt =>
        {
            Task.Run(() => ProcessTrialSubscriptions(_subscriptionRepository));
        });
    });
}

【讨论】:

  • 存在差异,TPL 示例具有误导性。
猜你喜欢
  • 2021-09-10
  • 2013-05-08
  • 1970-01-01
  • 1970-01-01
  • 2014-01-27
  • 2021-08-04
  • 1970-01-01
  • 2010-10-28
  • 1970-01-01
相关资源
最近更新 更多