为什么这段代码同步运行?

时间:2021-12-26 00:56:10

I am trying to understand concurrency by doing it in code. I have a code snippet which I thought was running asynchronously. But when I put the debug writeline statements in, I found that it is running synchronously. Can someone explain what I need to do differently to push ComputeBB() onto another thread using Task.Something?

我试图通过在代码中执行它来理解并发性。我有一个代码片段,我认为它是异步运行的。但是当我把调试writeline语句放入其中时,我发现它正在同步运行。有人可以解释我需要做什么不同的使用Task.Something将ComputeBB()推送到另一个线程?

Clarification I want this code to run ComputeBB in some other thread so that the main thread will keep on running without blocking.

澄清我想让这段代码在其他一些线程中运行ComputeBB,这样主线程就会继续运行而不会阻塞。

Here is the code:

这是代码:

{
    // part of the calling method
     Debug.WriteLine("About to call ComputeBB");
     returnDTM.myBoundingBox = await Task.Run(() => returnDTM.ComputeBB());
     Debug.WriteLine("Just called await ComputBB.");
     return returnDTM;
}

  private ptsBoundingBox2d ComputeBB()
  {
     Debug.WriteLine("Starting ComputeBB.");
     Stopwatch sw = new Stopwatch(); sw.Start();
     var point1 = this.allPoints.FirstOrDefault().Value;
     var returnBB = new ptsBoundingBox2d(
        point1.x, point1.y, point1.z, point1.x, point1.y, point1.z);
     Parallel.ForEach(this.allPoints,
        p => returnBB.expandByPoint(p.Value.x, p.Value.y, p.Value.z)
        );
     sw.Stop();
     Debug.WriteLine(String.Format("Compute BB took {0}", sw.Elapsed));
     return returnBB;
  }

Here is the output in the immediate window:

以下是即时窗口中的输出:

About to call ComputeBB
Starting ComputeBB.
Compute BB took 00:00:00.1790574
Just called await ComputBB.

Clarification If it were really running asynchronously it would be in this order:

澄清如果它真的是异步运行,它将按此顺序:

About to call ComputeBB
Just called await ComputBB.
Starting ComputeBB.
Compute BB took 00:00:00.1790574

But it is not.

但事实并非如此。

Elaboration The calling code has signature like so: private static async Task loadAsBinaryAsync(string fileName) At the next level up, though, I attempt to stop using async. So here is the call stack from top to bottom:

详细说明调用代码具有如下签名:private static async Task loadAsBinaryAsync(string fileName)但是,在下一级别,我尝试停止使用异步。所以这里是从上到下的调用堆栈:

  static void Main(string[] args)
  {
      aTinFile = ptsDTM.CreateFromExistingFile("TestSave.ptsTin");
      // more stuff
  }

  public static ptsDTM CreateFromExistingFile(string fileName)
  {
     ptsDTM returnTin = new ptsDTM();
     Task<ptsDTM> tsk = Task.Run(() => loadAsBinaryAsync(fileName));
     returnTin = tsk.Result;  // I suspect the problem is here.
     return retunTin;
  }

  private static async Task<ptsDTM> loadAsBinaryAsync(string fileName)
  {
      // do a lot of processing
     Debug.WriteLine("About to call ComputeBB");
     returnDTM.myBoundingBox = await Task.Run(() => returnDTM.ComputeBB());
     Debug.WriteLine("Just called await ComputBB.");
     return returnDTM;
  }

2 个解决方案

#1


I have a code snippet which I thought was running asynchronously. But when I put the debug writeline statements in, I found that it is running synchronously.

我有一个代码片段,我认为它是异步运行的。但是当我把调试writeline语句放入其中时,我发现它正在同步运行。

await is used to asynchronously wait an operations completion. While doing so, it yields control back to the calling method until it's completion.

await用于异步等待操作完成。这样做时,它会将控制权交还给调用方法,直到完成为止。

what I need to do differently to push ComputeBB() onto another thread

我需要以不同的方式将ComputeBB()推送到另一个线程

It is already ran on a thread pool thread. If you don't want to asynchronously wait on it in a "fire and forget" fashion, don't await the expression. Note this will have an effect on exception handling. Any exception which occurs inside the provided delegate would be captured inside the given Task, if you don't await, there is a chance they will go about unhandled.

它已经在线程池线程上运行。如果您不想以“一劳永逸”的方式异步等待它,请不要等待表达式。请注意,这将对异常处理产生影响。在提供的委托中发生的任何异常都将在给定的任务中捕获,如果您没有等待,它们将有可能无法处理。

Edit:

Lets look at this piece of code:

让我们看看这段代码:

public static ptsDTM CreateFromExistingFile(string fileName)
{
   ptsDTM returnTin = new ptsDTM();
   Task<ptsDTM> tsk = Task.Run(() => loadAsBinaryAsync(fileName));
   returnTin = tsk.Result;  // I suspect the problem is here.
   return retunTin;
}

What you're currently doing is synchronously blocking when you use tsk.Result. Also, for some reason you're calling Task.Run twice, once in each method. That is unnecessary. If you want to return your ptsDTM instance from CreateFromExistingFile, you will have to await it, there is no getting around that. "Fire and Forget" execution doesn't care about the result, at all. It simply wants to start whichever operation it needs, if it fails or succeeds is usually a non-concern. That is clearly not the case here.

当你使用tsk.Result时,你当前正在做的是同步阻塞。此外,由于某种原因,您在每个方法中调用两次Task.Run。这是不必要的。如果你想从CreateFromExistingFile返回你的ptsDTM实例,你将不得不等待它,没有解决这个问题。 “火和忘记”执行根本不关心结果。它只是想要启动它需要的任何操作,如果它失败或成功通常是不关心的。这显然不是这种情况。

You'll need to do something like this:

你需要做这样的事情:

private PtsDtm LoadAsBinary(string fileName)
{
   Debug.WriteLine("About to call ComputeBB");
   returnDTM.myBoundingBox = returnDTM.ComputeBB();
   Debug.WriteLine("Just called ComputeBB.");

   return returnDTM;
}

And then somewhere up higher up the call stack, you don't actually need CreateFromExistingFiles, simply call:

然后在调用堆栈的某个地方,你实际上并不需要CreateFromExistingFiles,只需调用:

Task.Run(() => LoadAsBinary(fileName));

When needed.

Also, please, read the C# naming conventions, which you're currently not following.

另外,请阅读您目前没有关注的C#命名约定。

#2


await's whole purpose is in adding the synchronicity back in asynchronous code. This allows you to easily partition the parts that are happenning synchronously and asynchronously. Your example is absurd in that it never takes any advantage whatsoever of this - if you just called the method directly instead of wrapping it in Task.Run and awaiting that, you would have had the exact same result (with less overhead).

await的全部目的是在异步代码中添加同步性。这使您可以轻松地对同步和异步发生的部件进行分区。你的例子是荒谬的,因为它永远不会带来任何好处 - 如果你只是直接调用方法而不是将它包装在Task.Run中并等待它,你将得到完全相同的结果(具有更少的开销)。

Consider this, though:

考虑一下,但是:

await
  Task.WhenAll
  (
    loadAsBinaryAsync(fileName1),
    loadAsBinaryAsync(fileName2),
    loadAsBinaryAsync(fileName3)
  );

Again, you have the synchronicity back (await functions as the synchronization barrier), but you've actually performed three independent operations asynchronously with respect to each other.

同样,你有同步性(等待作为同步障碍的功能),但你实际上已经相互异步执行了三个独立的操作。

Now, there's no reason to do something like this in your code, since you're using Parallel.ForEach at the bottom level - you're already using the CPU to the max (with unnecessary overhead, but let's ignore that for now).

现在,没有理由在你的代码中做这样的事情,因为你在底层使用Parallel.ForEach--你已经在最大程度上使用了CPU(带来了不必要的开销,但是现在让我们忽略它)。

So the basic usage of await is actually to handle asynchronous I/O rather than CPU work - apart from simplifying code that relies on some parts of CPU work being synchronised and some not (e.g. you have four threads of execution that simultaneously process different parts of the problem, but at some point have to be reunited to make sense of the individual parts - look at the Barrier class, for example). This includes stuff like "making sure the UI doesn't block while some CPU intensive operation happens in the background" - this makes the CPU work asynchronous with respect to the UI. But at some point, you still want to reintroduce the synchronicity, to make sure you can display the results of the work on the UI.

因此,await的基本用法实际上是处理异步I / O而不是CPU工作 - 除了简化依赖于同步CPU工作的某些部分的代码而不是某些部分(例如,您有四个执行线程同时处理不同的部分)问题,但在某些时候必须团聚,以了解各个部分 - 例如,看看障碍类。这包括诸如“确保UI不会在某些CPU密集型操作在后台发生时阻塞”这样的东西 - 这使得CPU在UI方面工作异步。但在某些时候,您仍然希望重新引入同步性,以确保您可以在UI上显示工作结果。

Consider this winforms code snippet:

考虑一下这个winforms代码片段:

async void btnDoStuff_Click(object sender, EventArgs e)
{
  lblProgress.Text = "Calculating...";
  var result = await DoTheUltraHardStuff();
  lblProgress.Text = "Done! The result is " + result;
}

(note that the method is async void, not async Task nor async Task<T>)

(注意该方法是async void,不是async Task,也不是异步Task )

What happens is that (on the GUI thread) the label is first assigned the text Calculating..., then the asynchronous DoTheUltraHardStuff method is scheduled, and then, the method returns. Immediately. This allows the GUI thread to do whatever it needs to do. However - as soon as the asynchronous task is complete and the GUI is free to handle the callback, the execution of btnDoStuff_Click will continue with the result already given (or an exception thrown, of course), back on the GUI thread, allowing you to set the label to the new text including the result of the asynchronous operation.

会发生什么(在GUI线程上)首先为标签分配文本Calculating ...,然后调度异步DoTheUltraHardStuff方法,然后返回该方法。立即。这允许GUI线程做它需要做的任何事情。但是 - 只要异步任务完成并且GUI可以*处理回调,btnDoStuff_Click的执行将继续执行已经给出的结果(当然是抛出异常),返回GUI线程,允许您将标签设置为新文本,包括异步操作的结果。

Asynchronicity is not an absolute property - stuff is asynchronous to some other stuff, and synchronous to some other stuff. It only makes sense with respect to some other stuff.

异步性不是绝对的属性 - 东西与其他东西是异步的,并且与其他东西同步。它只对其他一些东西有意义。

Hopefully, now you can go back to your original code and understand the part you've misunderstood before. The solutions are multiple, of course, but they depend a lot on how and why you're trying to do what you're trying to do. I suspect you don't actually need to use Task.Run or await at all - the Parallel.ForEach already tries to distribute the CPU work over multiple CPU cores, and the only thing you could do is to make sure other code doesn't have to wait for that work to finish - which would make a lot of sense in a GUI application, but I don't see how it would be useful in a console application with the singular purpose of calculating that single thing.

希望现在您可以回到原始代码并理解您之前误解过的部分。当然,这些解决方案是多方面的,但它们很大程度上依赖于你尝试做你想做的事情的方式和原因。我怀疑你实际上并不需要使用Task.Run或等待 - Parallel.ForEach已经尝试在多个CPU内核上分配CPU工作,你唯一能做的就是确保其他代码没有必须等待这项工作完成 - 这在GUI应用程序中会有很大意义,但我不知道它在控制台应用程序中如何有用,它具有计算单一事物的单一目的。

So yes, you can actually use await for fire-and-forget code - but only as part of code that doesn't prevent the code you want to continue from executing. For example, you could have code like this:

所以,是的,您实际上可以使用等待代码为fire-and-forget代码 - 但仅作为代码的一部分,不会阻止您希望继续执行的代码。例如,您可以使用以下代码:

Task<string> result = SomeHardWorkAsync();
Debug.WriteLine("After calling SomeHardWorkAsync");
DoSomeOtherWorkInTheMeantime();
Debug.WriteLine("Done other work.");

Debug.WriteLine("Got result: " + (await result));

This allows SomeHardWorkAsync to execute asynchronously with respect to DoSomeOtherWorkInTheMeantime but not with respect to await result. And of course, you can use awaits in SomeHardWorkAsync without trashing the asynchronicity between SomeHardWorkAsync and DoSomeOtherWorkInTheMeantime.

这允许SomeHardWorkAsync相对于DoSomeOtherWorkInTheMeantime异步执行,但不等待等待结果。当然,您可以在SomeHardWorkAsync中使用等待,而不会破坏SomeHardWorkAsync和DoSomeOtherWorkInTheMeantime之间的异步性。

The GUI example I've shown way above just takes advantage of handling the continuation as something that happens after the task completes, while ignoring the Task created in the async method (there really isn't much of a difference between using async void and async Task when you ignore the result). So for example, to fire-and-forget your method, you could use code like this:

我上面显示的G​​UI示例只是利用处理延续作为任务完成后发生的事情,同时忽略在异步方法中创建的任务(使用async void和async之间确实没有太大区别忽略结果时的任务)。例如,要发射并忘记您的方法,您可以使用如下代码:

async void Fire(string filename)
{
  var result = await ProcessFileAsync(filename);
  DoStuffWithResult(result);
}

Fire("MyFile");

This will cause DoStuffWithResult to execute as soon as result is ready, while the method Fire itself will return immediately after executing ProcessFileAsync (up to the first await or any explicit return someTask).

这将导致DoStuffWithResult在结果准备就绪时立即执行,而方法Fire本身将在执行ProcessFileAsync后立即返回(直到第一次等待或任何显式返回someTask)。

This pattern is usually frowned upon - there really isn't any reason to return void out of an async method (apart from event handlers); you could just as easily return Task (or even Task<T> depending on the scenario), and let the caller decide whether he wants his code to execute synchronously in respect to yours or not.

这个模式通常不受欢迎 - 实际上没有任何理由从异步方法中返回void(除了事件处理程序);你可以很容易地返回Task(甚至是Task ,具体取决于场景),并让调用者决定他是否希望他的代码与你的代码同步执行。

Again,

async Task FireAsync(string filename)
{
  var result = await ProcessFileAsync(filename);
  DoStuffWithResult(result);
}

Fire("MyFile");

does the same thing as using async void, except that the caller can decide what to do with the asynchronous task. Perhaps he wants to launch two of those in parallel and continue after all are done? He can just await Task.WhenAll(Fire("1"), Fire("2")). Or he just wants that stuff to happen completely asynchronously with respect to his code, so he'll just call Fire("1") and ignore the resulting Task (of course, ideally, you at the very least want to handle possible exceptions).

与使用async void做同样的事情,除了调用者可以决定如何处理异步任务。也许他想要并行启动其中两个,并在完成后继续?他可以等待Task.WhenAll(Fire(“1”),Fire(“2”))。或者他只是希望这些东西完全与他的代码异步发生,所以他只需要调用Fire(“1”)并忽略生成的Task(当然,理想情况下,你至少想要处理可能的异常) 。

#1


I have a code snippet which I thought was running asynchronously. But when I put the debug writeline statements in, I found that it is running synchronously.

我有一个代码片段,我认为它是异步运行的。但是当我把调试writeline语句放入其中时,我发现它正在同步运行。

await is used to asynchronously wait an operations completion. While doing so, it yields control back to the calling method until it's completion.

await用于异步等待操作完成。这样做时,它会将控制权交还给调用方法,直到完成为止。

what I need to do differently to push ComputeBB() onto another thread

我需要以不同的方式将ComputeBB()推送到另一个线程

It is already ran on a thread pool thread. If you don't want to asynchronously wait on it in a "fire and forget" fashion, don't await the expression. Note this will have an effect on exception handling. Any exception which occurs inside the provided delegate would be captured inside the given Task, if you don't await, there is a chance they will go about unhandled.

它已经在线程池线程上运行。如果您不想以“一劳永逸”的方式异步等待它,请不要等待表达式。请注意,这将对异常处理产生影响。在提供的委托中发生的任何异常都将在给定的任务中捕获,如果您没有等待,它们将有可能无法处理。

Edit:

Lets look at this piece of code:

让我们看看这段代码:

public static ptsDTM CreateFromExistingFile(string fileName)
{
   ptsDTM returnTin = new ptsDTM();
   Task<ptsDTM> tsk = Task.Run(() => loadAsBinaryAsync(fileName));
   returnTin = tsk.Result;  // I suspect the problem is here.
   return retunTin;
}

What you're currently doing is synchronously blocking when you use tsk.Result. Also, for some reason you're calling Task.Run twice, once in each method. That is unnecessary. If you want to return your ptsDTM instance from CreateFromExistingFile, you will have to await it, there is no getting around that. "Fire and Forget" execution doesn't care about the result, at all. It simply wants to start whichever operation it needs, if it fails or succeeds is usually a non-concern. That is clearly not the case here.

当你使用tsk.Result时,你当前正在做的是同步阻塞。此外,由于某种原因,您在每个方法中调用两次Task.Run。这是不必要的。如果你想从CreateFromExistingFile返回你的ptsDTM实例,你将不得不等待它,没有解决这个问题。 “火和忘记”执行根本不关心结果。它只是想要启动它需要的任何操作,如果它失败或成功通常是不关心的。这显然不是这种情况。

You'll need to do something like this:

你需要做这样的事情:

private PtsDtm LoadAsBinary(string fileName)
{
   Debug.WriteLine("About to call ComputeBB");
   returnDTM.myBoundingBox = returnDTM.ComputeBB();
   Debug.WriteLine("Just called ComputeBB.");

   return returnDTM;
}

And then somewhere up higher up the call stack, you don't actually need CreateFromExistingFiles, simply call:

然后在调用堆栈的某个地方,你实际上并不需要CreateFromExistingFiles,只需调用:

Task.Run(() => LoadAsBinary(fileName));

When needed.

Also, please, read the C# naming conventions, which you're currently not following.

另外,请阅读您目前没有关注的C#命名约定。

#2


await's whole purpose is in adding the synchronicity back in asynchronous code. This allows you to easily partition the parts that are happenning synchronously and asynchronously. Your example is absurd in that it never takes any advantage whatsoever of this - if you just called the method directly instead of wrapping it in Task.Run and awaiting that, you would have had the exact same result (with less overhead).

await的全部目的是在异步代码中添加同步性。这使您可以轻松地对同步和异步发生的部件进行分区。你的例子是荒谬的,因为它永远不会带来任何好处 - 如果你只是直接调用方法而不是将它包装在Task.Run中并等待它,你将得到完全相同的结果(具有更少的开销)。

Consider this, though:

考虑一下,但是:

await
  Task.WhenAll
  (
    loadAsBinaryAsync(fileName1),
    loadAsBinaryAsync(fileName2),
    loadAsBinaryAsync(fileName3)
  );

Again, you have the synchronicity back (await functions as the synchronization barrier), but you've actually performed three independent operations asynchronously with respect to each other.

同样,你有同步性(等待作为同步障碍的功能),但你实际上已经相互异步执行了三个独立的操作。

Now, there's no reason to do something like this in your code, since you're using Parallel.ForEach at the bottom level - you're already using the CPU to the max (with unnecessary overhead, but let's ignore that for now).

现在,没有理由在你的代码中做这样的事情,因为你在底层使用Parallel.ForEach--你已经在最大程度上使用了CPU(带来了不必要的开销,但是现在让我们忽略它)。

So the basic usage of await is actually to handle asynchronous I/O rather than CPU work - apart from simplifying code that relies on some parts of CPU work being synchronised and some not (e.g. you have four threads of execution that simultaneously process different parts of the problem, but at some point have to be reunited to make sense of the individual parts - look at the Barrier class, for example). This includes stuff like "making sure the UI doesn't block while some CPU intensive operation happens in the background" - this makes the CPU work asynchronous with respect to the UI. But at some point, you still want to reintroduce the synchronicity, to make sure you can display the results of the work on the UI.

因此,await的基本用法实际上是处理异步I / O而不是CPU工作 - 除了简化依赖于同步CPU工作的某些部分的代码而不是某些部分(例如,您有四个执行线程同时处理不同的部分)问题,但在某些时候必须团聚,以了解各个部分 - 例如,看看障碍类。这包括诸如“确保UI不会在某些CPU密集型操作在后台发生时阻塞”这样的东西 - 这使得CPU在UI方面工作异步。但在某些时候,您仍然希望重新引入同步性,以确保您可以在UI上显示工作结果。

Consider this winforms code snippet:

考虑一下这个winforms代码片段:

async void btnDoStuff_Click(object sender, EventArgs e)
{
  lblProgress.Text = "Calculating...";
  var result = await DoTheUltraHardStuff();
  lblProgress.Text = "Done! The result is " + result;
}

(note that the method is async void, not async Task nor async Task<T>)

(注意该方法是async void,不是async Task,也不是异步Task )

What happens is that (on the GUI thread) the label is first assigned the text Calculating..., then the asynchronous DoTheUltraHardStuff method is scheduled, and then, the method returns. Immediately. This allows the GUI thread to do whatever it needs to do. However - as soon as the asynchronous task is complete and the GUI is free to handle the callback, the execution of btnDoStuff_Click will continue with the result already given (or an exception thrown, of course), back on the GUI thread, allowing you to set the label to the new text including the result of the asynchronous operation.

会发生什么(在GUI线程上)首先为标签分配文本Calculating ...,然后调度异步DoTheUltraHardStuff方法,然后返回该方法。立即。这允许GUI线程做它需要做的任何事情。但是 - 只要异步任务完成并且GUI可以*处理回调,btnDoStuff_Click的执行将继续执行已经给出的结果(当然是抛出异常),返回GUI线程,允许您将标签设置为新文本,包括异步操作的结果。

Asynchronicity is not an absolute property - stuff is asynchronous to some other stuff, and synchronous to some other stuff. It only makes sense with respect to some other stuff.

异步性不是绝对的属性 - 东西与其他东西是异步的,并且与其他东西同步。它只对其他一些东西有意义。

Hopefully, now you can go back to your original code and understand the part you've misunderstood before. The solutions are multiple, of course, but they depend a lot on how and why you're trying to do what you're trying to do. I suspect you don't actually need to use Task.Run or await at all - the Parallel.ForEach already tries to distribute the CPU work over multiple CPU cores, and the only thing you could do is to make sure other code doesn't have to wait for that work to finish - which would make a lot of sense in a GUI application, but I don't see how it would be useful in a console application with the singular purpose of calculating that single thing.

希望现在您可以回到原始代码并理解您之前误解过的部分。当然,这些解决方案是多方面的,但它们很大程度上依赖于你尝试做你想做的事情的方式和原因。我怀疑你实际上并不需要使用Task.Run或等待 - Parallel.ForEach已经尝试在多个CPU内核上分配CPU工作,你唯一能做的就是确保其他代码没有必须等待这项工作完成 - 这在GUI应用程序中会有很大意义,但我不知道它在控制台应用程序中如何有用,它具有计算单一事物的单一目的。

So yes, you can actually use await for fire-and-forget code - but only as part of code that doesn't prevent the code you want to continue from executing. For example, you could have code like this:

所以,是的,您实际上可以使用等待代码为fire-and-forget代码 - 但仅作为代码的一部分,不会阻止您希望继续执行的代码。例如,您可以使用以下代码:

Task<string> result = SomeHardWorkAsync();
Debug.WriteLine("After calling SomeHardWorkAsync");
DoSomeOtherWorkInTheMeantime();
Debug.WriteLine("Done other work.");

Debug.WriteLine("Got result: " + (await result));

This allows SomeHardWorkAsync to execute asynchronously with respect to DoSomeOtherWorkInTheMeantime but not with respect to await result. And of course, you can use awaits in SomeHardWorkAsync without trashing the asynchronicity between SomeHardWorkAsync and DoSomeOtherWorkInTheMeantime.

这允许SomeHardWorkAsync相对于DoSomeOtherWorkInTheMeantime异步执行,但不等待等待结果。当然,您可以在SomeHardWorkAsync中使用等待,而不会破坏SomeHardWorkAsync和DoSomeOtherWorkInTheMeantime之间的异步性。

The GUI example I've shown way above just takes advantage of handling the continuation as something that happens after the task completes, while ignoring the Task created in the async method (there really isn't much of a difference between using async void and async Task when you ignore the result). So for example, to fire-and-forget your method, you could use code like this:

我上面显示的G​​UI示例只是利用处理延续作为任务完成后发生的事情,同时忽略在异步方法中创建的任务(使用async void和async之间确实没有太大区别忽略结果时的任务)。例如,要发射并忘记您的方法,您可以使用如下代码:

async void Fire(string filename)
{
  var result = await ProcessFileAsync(filename);
  DoStuffWithResult(result);
}

Fire("MyFile");

This will cause DoStuffWithResult to execute as soon as result is ready, while the method Fire itself will return immediately after executing ProcessFileAsync (up to the first await or any explicit return someTask).

这将导致DoStuffWithResult在结果准备就绪时立即执行,而方法Fire本身将在执行ProcessFileAsync后立即返回(直到第一次等待或任何显式返回someTask)。

This pattern is usually frowned upon - there really isn't any reason to return void out of an async method (apart from event handlers); you could just as easily return Task (or even Task<T> depending on the scenario), and let the caller decide whether he wants his code to execute synchronously in respect to yours or not.

这个模式通常不受欢迎 - 实际上没有任何理由从异步方法中返回void(除了事件处理程序);你可以很容易地返回Task(甚至是Task ,具体取决于场景),并让调用者决定他是否希望他的代码与你的代码同步执行。

Again,

async Task FireAsync(string filename)
{
  var result = await ProcessFileAsync(filename);
  DoStuffWithResult(result);
}

Fire("MyFile");

does the same thing as using async void, except that the caller can decide what to do with the asynchronous task. Perhaps he wants to launch two of those in parallel and continue after all are done? He can just await Task.WhenAll(Fire("1"), Fire("2")). Or he just wants that stuff to happen completely asynchronously with respect to his code, so he'll just call Fire("1") and ignore the resulting Task (of course, ideally, you at the very least want to handle possible exceptions).

与使用async void做同样的事情,除了调用者可以决定如何处理异步任务。也许他想要并行启动其中两个,并在完成后继续?他可以等待Task.WhenAll(Fire(“1”),Fire(“2”))。或者他只是希望这些东西完全与他的代码异步发生,所以他只需要调用Fire(“1”)并忽略生成的Task(当然,理想情况下,你至少想要处理可能的异常) 。