2016-10-28 52 views
2

昨天我问了一个question,答案很好。但是现在我想了解await的作用以及任务执行的工作方式。有和没有等待操作员的任务执行

我读过关于await:await运算符应用于异步方法中的任务,以挂起方法的执行,直到等待完成的任务完成。该任务代表正在进行的工作(来自msdn网站)。

Task.run:Run方法允许您在单个方法调用中创建和执行任务,并且是对StartNew方法(来自msdn站点)的简单替代方案。

现在,随着代码:

 public async Task YourFunc() 
     { 
      Exception error = null; 
      try 
      { 
       var task = Task.Run(() => 
         { 
          Thread.Sleep(3000); 
          throw new ArgumentException("test argument exception"); 
         }); 
       var completed = task.IsCompleted; 
       var faulted = task.IsFaulted; 
       Console.WriteLine(completed); 
       Console.WriteLine(faulted); 
      } 
      catch (Exception ex) 
      { 
       error = ex; 
      } 
      this.MigrationProcessCompleted(error); 
     } 

我已经删除了的await操作,我已经设置断点上线Console.WriteLine(completed);。为什么即使在这个断点等待2-3分钟后,任务也没有完成,没有出错?我给自己定任务的代码和内部断点例外是trown,所以任务必须被标记为故障或至少完成...

+1

你在2-3分钟后检查什么?'task.IsFaulted'或简单的'faulted'变量在3分钟前对这个值进行采样? –

+0

task.IsFaulted –

+1

没有等待,Task完成比YourFunc退出的时间晚了很多,所以尝试抓取并且实际上整个方法完全没用。完成和错误都是错误的,因为在你点击它们的那一刻,任务还没有完成或者出错。至于断点 - 所有线程在断点处暂停,所以无论等待多长时间 - 任务仍在进行中。 – Evk

回答

1

没有人真的回答了你的问题。如果您等待超过3秒钟,您有合理的期望看到task.isCompletedtask.isFaulted为真。

您的期望没有错。这里的问题只是调试器的工作方式。当你停在那个等待事件的断点处时,所有其他线程也停止,所以没有任何进展,并且异常还没有被抛出。

这仅仅是一个调试的怪癖,你有几种选择,如果你想看到你所期望的结果:

  1. 冻结而不是使用断点的线程。为了做到这一点,在var task=Task.Run()之后放置一个断点,然后在调试器中打开“线程”窗口。找到当前线程(带有黄色箭头),右键单击它并选择“冻结”,然后按F8或单击继续让应用程序继续运行。 3-4秒后,单击调试器上的“暂停”按钮并再次双击冻结的线程。您现在可以检查task.IsCompletedtask.IsFaulted的值,它们应该是正确的。
  2. 添加超时您检查任务之前:

代码:

var task = Task.Run(() =>{ 
      Thread.Sleep(3000); 
      throw new ArgumentException("test argument exception"); 
     }); 
     Thread.Sleep(5000); 
     var completed = task.IsCompleted; 
     var faulted = task.IsFaulted; 
     Console.WriteLine("Completed ::" + completed); 
     Console.WriteLine("Faulted ::" + faulted); 

现在你可以把一个断点Thread.Sleep(5000)后,确认任务发生故障/完成。

请记住,正如其他人所说,立即使用awaitedTask.Run()几乎总是一个错误。请记住,async/await的全部内容是释放当前线程,所以它不会做无用的等待。如果你做了Task.Run然后await你没有取得任何成就,因为你只是释放当前线程(把它放回线程池),但Task.Run()将占用线程池中的一个线程。从概念上讲,你所做的一切都是将你的工作形式从一个线程标识移动到另一个线程,而没有真正的收益(即使你的工作在CPU上)。

在这种情况下,您最好不要在做Task.Run而只是在当前线程上同步执行工作。

+0

对于实际的应用程序,除了等待任务以外,没有别的办法来确保调用者可以接收结果/例外。 正如我所提到的,当理想的异步除了调用线程之外没有其他线程时,由于调用线程池线程,这不是真正的异步方式。建议不要'等待Task.Run()'不正确,请建议可以在实际应用中使用异步方法的替代方法,我相信'Thread.Sleep'只是为了调试应用程序。 –

+0

必须说,与调试器冻结线程相关的问题是很好和正确的,我错过了,因为整个焦点是使用“等待”正确设计'Async'方法。 –

+0

他的问题没有听到这是任何真正的应用。这只是一次性代码,他只是想了解它是如何工作的。他的问题是关于他为什么在调试时没有看到他期望看到的内容。所以我专注于回答这个问题。 – dtabuenc

0

退房一些修改你的代码下面:

几个重要要点:

  • 当前实现是不是真正Async,因为它创造了一个同步逻辑的包装,调用线程池中的线程,在现实中there's no thread (Stepehen Cleary)
  • 守则并有助于释放调用线程,但与真正异步操作不同,它是计算绑定操作,它需要一个单独的线程而不是真正的IO操作,它在IO完成端口(基于硬件的队列)上执行
  • 正如您可能已经理解的,因为主机进程退出时没有执行await,因为主机进程退出,所以无法获取异常信息,因为任务在线程池线程上执行,后者在后台,前台没有任何东西可以接收它
  • await does help在解包和捕获任务异常,但主机进程仍然是重要的知道细节
  • ConfigureAwait(false).GetAwaiter().GetResult()仅用于启用控制台程序(作为主机)Catch t他例外,否则这不是一个建议的模式,并且对于像ASP.net MVC这样的标准异步调用堆栈,简单的await会在调用堆栈顶部做需求
  • 另外注意ConfigureAwait(false)对于Console,它们没有什么UI /同步环境,否则就不是必需的,直到除非启用完整的调用链忽略上下文贯穿
  • 如果你正在寻找的异步方式的情况下完成,然后检查了Task Completion Source
  • 另请检查Why Task StartNew is Dangerous

    class Program 
        { 
        static void Main(string[] args) 
        { 
         try 
         { 
          Program p = new Program(); 
          p.YourFunc().ConfigureAwait(false).GetAwaiter().GetResult(); 
         } 
         catch (Exception) 
         { 
          Console.WriteLine("Outer Exception"); 
         } 
        } 
    
        public async Task YourFunc() 
        { 
         Exception error = null; 
         try 
         { 
          var task = Task.Run(() => 
          { 
           Thread.Sleep(3000); 
           throw new ArgumentException("test argument exception"); 
          }); 
    
          await task; 
    
          var completed = task.IsCompleted; 
          var faulted = task.IsFaulted; 
          Console.WriteLine("Completed ::" + completed); 
          Console.WriteLine("Faulted ::" + faulted); 
         } 
         catch (Exception ex) 
         { 
          Console.WriteLine("Inner Exception"); 
          throw ex; 
         } 
         // Commented (No Implementation) 
         //this.MigrationProcessCompleted(error); 
        } 
        } 
    
+0

你对'ConfigureAwait(false)'的建议是倒退的。在控制台应用程序中,根本不需要'ConfiugreAwait(false)',因为没有可能导致性能问题或死锁的UI线程或ASP.net同步上下文。 'ConfigureAwait(false)'对UI应用程序和可能的ASP.NET应用程序至关重要,因为它们使用同步上下文来限制可以使用的线程(导致性能问题或死锁)。 – dtabuenc

+0

@dtabuenc,请阅读完整的一系列详细信息,我自己建议,这不是一个理想的用法,不能用于生产,它只用于调试应用程序,以便可以显示Inner和Outer异常给用户,因为调用线程仍然可以接收异常。我可以通过使用'GetAwaiter()。GetResult()'来管理,但'ConfigureAwait(false)'确保我们完全忽略了上下文(如果有的话)。 –

+0

@dtabuenc,同样对于任何Async方法来说,解开异常的理想方式总是“await”,当不使用时会导致OP所建议的情况,背景中的异常从未被捕获,因为没有人可以接收它。 –

相关问题