为什么此代码同步运行?

时间:2015-05-04 04:52:58

标签: c# multithreading async-await task-parallel-library task

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

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

以下是代码:

{
    // 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;
  }

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

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

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

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

但事实并非如此。

精化 调用代码具有如下签名: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 个答案:

答案 0 :(得分:8)

  

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

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

  

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

它已经在线程池线程上运行。如果你不想在火灾中异步等待并忘记&#34;时尚,不要await表达。注意这会对异常处理产生影响。在提供的委托中发生的任何异常都将在给定的Task内捕获,如果您不await,则有可能无法处理。

修改

让我们看看这段代码:

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;
}

当您使用tsk.Result时,您当前正在执行的操作是同步阻止。此外,出于某种原因,您需要两次调用Task.Run,每种方法一次。这是不必要的。如果您要从ptsDTM返回CreateFromExistingFile个实例,则必须await它,没有解决这个问题。 &#34;火与忘记&#34;执行根本不关心结果。它只是想要启动它需要的任何操作,如果它失败或成功通常是不关心的。这显然不是这种情况。

你需要做这样的事情:

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

   return returnDTM;
}

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

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

需要时。

另外,请阅读您当前未关注的C# naming conventions

答案 1 :(得分:2)

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

考虑一下,但是:

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

同样,您具有同步性(await作为同步障碍),但您实际上已经相互执行了三个独立的

现在,您没有理由在代码中执行此类操作,因为您在底层使用Parallel.ForEach - 您已经在最大程度上使用CPU了(有不必要的开销,但暂时忽略它。)

因此await的基本用法实际上是处理异步 I / O 而不是CPU工作 - 除了简化依赖于同步CPU工作的某些部分的代码而不是(例如,您有四个执行线程同时处理问题的不同部分,但在某些时候必须重新组合才能理解各个部分 - 例如,查看Barrier类。这包括像&#34;确保用户界面不会阻塞,同时在后台进行一些CPU密集型操作&#34; - 这使得CPU相对于UI 异步工作。但在某些时候,您仍然希望重新引入同步性,以确保您可以在UI上显示工作结果。

考虑这个winforms代码片段:

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

(请注意,该方法为async void,而非async Taskasync Task<T>

在(在GUI线程上)首先为标签分配文本Calculating...,然后调度异步DoTheUltraHardStuff方法,然后返回该方法。立即。这允许GUI线程做它需要做的任何事情。 然而 - 一旦异步任务完成且GUI可以自由处理回调,btnDoStuff_Click的执行将继续与{{1}已经给出(或者抛出异常),返回GUI线程,允许您将标签设置为新文本,包括异步操作的结果。

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

希望您现在可以回到原始代码并了解您之前误解过的部分。当然,这些解决方案是多方面的,但它们在很大程度上取决于您如何以及为什么要尝试做您正在尝试做的事情。我怀疑你实际上根本不需要使用resultTask.Run - await已经尝试在多个CPU内核上分配CPU工作,而且你唯一能做的就是做的是确保其他代码不必等待该工作完成 - 这在GUI应用程序中很有意义,但我不知道它在控制台应用程序中是如何有用的用于计算单一事物的单一目的。

所以,是的,你可以实际使用Parallel.ForEach代码为“即发即弃” - 但作为代码的一部分,不会阻止您希望继续执行的代码。例如,您可以使用以下代码:

await

这允许Task<string> result = SomeHardWorkAsync(); Debug.WriteLine("After calling SomeHardWorkAsync"); DoSomeOtherWorkInTheMeantime(); Debug.WriteLine("Done other work."); Debug.WriteLine("Got result: " + (await result)); SomeHardWorkAsync异步执行,而DoSomeOtherWorkInTheMeantime异步执行。当然,您可以在await result中使用await,而不会破坏SomeHardWorkAsyncSomeHardWorkAsync之间的异步性。

我上面显示的G​​UI示例只是利用处理延续作为任务完成后发生的事情,同时忽略DoSomeOtherWorkInTheMeantime方法中创建的Task(实际上当你忽略结果时,使用asyncasync void之间的区别并不大。例如,要发射并忘记您的方法,您可以使用以下代码:

async Task

这将导致async void Fire(string filename) { var result = await ProcessFileAsync(filename); DoStuffWithResult(result); } Fire("MyFile"); DoStuffWithResult准备好后立即执行,而方法result本身将在执行Fire后立即返回(直到第一个{{} 1}}或任何明确的ProcessFileAsync)。

这种模式通常不受欢迎 - 没有任何理由让await退出异步方法(除了事件处理程序);您可以轻松地返回return someTask(甚至void,具体取决于场景),并让调用者决定是否希望他的代码与您的代码同步执行。

再次

Task

与使用Task<T>做同样的事情,除了调用者可以决定如何处理异步任务。也许他想要并行启动其中两个,并在完成后继续?他可以async Task FireAsync(string filename) { var result = await ProcessFileAsync(filename); DoStuffWithResult(result); } Fire("MyFile"); 。或者他只是希望这些东西完全与他的代码异步发生,所以他只需要调用async void并忽略生成的await Task.WhenAll(Fire("1"), Fire("2"))(当然,理想情况下,你至少需要处理可能的例外情况。)