It’s been awesome seeing the level of interest developers have had for the Async CTP and how much usage it’s getting.  Of course, with any new technology there are bound to be some hiccups.  One issue I’ve seen arise now multiple times is developers accidentally deadlocking their application by blocking their UI thread, so I thought it would be worthwhile to take a few moments to explore the common cause of this and how to avoid such predicaments.

At its core, the new async language functionality aims to restore the ability for developers to write the sequential, imperative code they’re used to writing, but to have it be asynchronous in nature rather than synchronous.  That means that when operations would otherwise tie up the current thread of execution, they’re instead offloaded elsewhere, allowing the current thread to make forward progress and do other useful work while, in effect, asynchronously waiting for the spawned operation to complete.  In both server and client applications, this can be crucial for application scalability, and in client applications in particular it’s also really useful for responsiveness.

Most UI frameworks, such as Windows Forms and WPF, utilize a message loop to receive and process incoming messages.  These messages include things like notifications of keys being typed on a keyboard, or buttons being clicked on a mouse, or controls in the user interface being manipulated, or the need to refresh an area of the window, or even the application sending itself a message dictating some code to be executed.  In response to these messages, the UI performs some action, such as redrawing its surface, or changing the text being displayed, or adding items to one of its controls., or running the code that was posted to it.  The “message loop” is typically literally a loop in code, where a thread continually waits for the next message to arrive, processes it, goes back to get the next message, processes it, and so on.  As long as that thread is able to quickly process messages as soon as they arrive, the application remains responsive, and the application’s users remain happy.  If, however, processing a particular message takes too long, the thread running the message loop code will be unable to pick up the next message in a timely fashion, and responsiveness will decrease.  This could take the form of pauses in responding to user input, and if the thread’s delays get bad enough (e.g. an infinite delay), the application “hanging”.

In a framework like Windows Forms or WPF, when a user clicks a button, that typically ends up sending a message to the message loop, which translates the message into a call to a handler of some kind, such as a method on the class representing the user interface, e.g.:

private void button1_Click(object sender, RoutedEventArgs e)       {            string s = LoadString();            textBox1.Text = s;        }

Here, when I click the button1 control, the message will inform WPF to invoke the button1_Click method, which will in turn run a method LoadString to get a string value, and store that string value into the textBox1 control’s Text property.  As long as LoadString is quick to execute, all is well, but the longer LoadString takes, the more time the UI thread is delayed inside button1_Click, unable to return to the message loop to pick up and process the next message.

To address that, we can choose to load the string asynchronously, meaning that rather than blocking the thread calling button1_Click from returning to the message loop until the string loading has completed, we’ll instead just have that thread launch the loading operation and then go back to the message loop.  Only when the loading operation completes will we then send another message to the message loop to say “hey, that loading operation you previously started is done, and you can pick up where you left off and continue executing.”  Imagine we had a method:

public Task<string> LoadStringAsync();

This method will return very quickly to its caller, handing back a .NET Task<string> object that represents the future completion of the asynchronous operation and its future result.  At some point in the future when the operation completes, the task object will be able to hand out the operations’ result, which could be the string in the case of successful loading, or an exception in the case of failure.  Either way, the task object provides several mechanisms to notify the holder of the object that the loading operation has completed.  One way is to synchronously block waiting for the task to complete, and that can be accomplished by calling the task’s Wait method, or by accessing its Result, which will implicitly wait until the operation has completed… in both of these cases, a call to these members will not complete until the operation has completed.  An alternative way is to receive an asynchronous callback, where you register with the task a delegate that will be invoked when the task completes.  That can be accomplished using one of the Task’s ContinueWith methods.  With ContinueWith, we can now rewrite our previous button1_Click method to not block the UI thread while we’re asynchronously waiting for the loading operation to complete:

private void button1_Click(object sender, RoutedEventArgs e)       {            Task<string> s = LoadStringAsync();            s.ContinueWith(delegate { textBox1.Text = s.Result; }); // warning: buggy        }

This does in fact asynchronously launch the loading operation, and then asynchronously run the code to store the result into the UI when the operation completes.  However, we now have a new problem.  UI frameworks like Windows Forms, WPF, and Silverlight all place a restriction on which threads are able to access UI controls, namely that the control can only be accessed from the thread that created it.  Here, however, we’re running the callback to update the Text of textBox1on some arbitrary thread, wherever the Task Parallel Library (TPL) implementation of ContinueWith happened to put it.  To address this, we need some way to get back to the UI thread.  Different UI frameworks provide different mechanisms for doing this, but in .NET they all take basically the same shape, a BeginInvoke method you can use to pass some code as a message to the UI thread to be processed:

private void button1_Click(object sender, RoutedEventArgs e)       {            Task<string> s = LoadStringAsync();            s.ContinueWith(delegate            {                Dispatcher.BeginInvoke(new Action(delegate                {                    textBox1.Text = s.Result;                }));            });        }

The .NET Framework further abstracts over these mechanisms for getting back to the UI thread, and in general a mechanism for posting some code to a particular context, through the SynchronizationContext class.  A framework can establish a current context, available through the SynchronizationContext.Current property, which provides a SynchronizationContext instance representing the current environment.  This instance’s Post method will marshal a delegate back to this environment to be invoked: in a WPF app, that means bringing you back to the dispatcher, or UI thread, you were previously on.  So, we can rewrite the previous code as follows:

private void button1_Click(object sender, RoutedEventArgs e)       {            var sc = SynchronizationContext.Current;            Task<string> s = LoadStringAsync();            s.ContinueWith(delegate            {                sc.Post(delegate { textBox1.Text = s.Result; }, null);            });        }

and in fact this pattern is so common, TPL in .NET 4 provides the TaskScheduler.FromCurrentSynchronizationContext() method, which allows you to do the same thing with code like:

private void button1_Click(object sender, RoutedEventArgs e)       {            LoadStringAsync().ContinueWith(s => textBox1.Text = s.Result,                 TaskScheduler.FromCurrentSynchronizationContext());        }

As mentioned, this works by “posting” the delegate back to the UI thread to be executed.  That posting is a message like any other, and it requires the UI thread to go through its message loop, pick up the message, and process it (which will result in invoking the posted delegate).  In order for the delegate to be invoked then, the thread first needs to return to the message loop, which means it must leave the button1_Click method.

Now, there’s still a fair amount of boilerplate code to write above, and it gets orders of magnitude worse when you start introducing more complicated flow control constructs, like conditionals and loops.  To address this, the new async language feature allows you to write this same code as:

private void button1_Click(object sender, RoutedEventArgs e)       {            string s = await LoadStringAsync();            textBox1.Text = s;        }

For all intents and purposes, this is the same as the previous code shown, and you can see how much cleaner it is… in fact, it’s close to identical  in the code required to our original synchronous implementation.  But, of course, this one is asynchronous: after calling LoadStringAsync and getting back the Task<string> object, the remainder of the function is hooked up as a callback that will be posted to the current SynchronizationContext in order to continue execution on the right thread when the loading is complete.  The compiler is layering on some really helpful syntactic sugar here.

Now things get interesting. Let’s imagine LoadStringAsync is implemented as follows:

static async Task<string> LoadStringAsync()       {            string firstName = await GetFirstNameAsync();            string lastName = await GetLastNameAsync();            return firstName + " " + lastName;        }

LoadStringAsync is implemented to first asynchronously retrieve a first name, then asynchronously retrieve a last name, and then return the concatenation of the two.  Notice that it’s using “await”, which, as pointed out previously, is similar to the aforementioned TPL code that uses a continuation to post back to the synchronization context that was current when the await was issued.  So, here’s the crucial point: for LoadStringAsync to complete (i.e. for it to have loaded all of its data and returned its concatenated string, completing the task it returned with that concatenated result), the delegates it posted to the UI thread must have completed.  If the UI thread is unable to get back to the message loop to process messages, it will be unable to pick up the posted delegates that resulted from the asynchronous operations in LoadStringAsync completing, which means the remainder of LoadStringAsync will not run, which means the Task<string> returned from LoadStringAsync will not complete.  It won’t complete until the relevant messages are processed by the message loop.

With that in mind, consider this (faulty) reimplementation of button1_Click:

private void button1_Click(object sender, RoutedEventArgs e)       {            Task<string> s = LoadStringAsync();            textBox1.Text = s.Result; // warning: buggy        }

There’s an exceedingly good chance that this code will hang your application.  The Task<string>.Result property is strongly typed as a String, and thus it can’t return until it has the valid result string to hand back; in other words, it blocks until the result is available.  We’re inside of button1_Click then blocking for LoadStringAsync to complete, but LoadStringAsync’s implementation depends on being able to post code asynchronously back to the UI to be executed, and the task returned from LoadStringAsync won’t complete until it does.  LoadStringAsync is waiting for button1_Click to complete, and button1_Click is waiting for LoadStringAsync to complete. Deadlock!

This problem can be exemplified easily without using any of this complicated machinery, e.g.:

private void button1_Click(object sender, RoutedEventArgs e) {     var mre = new ManualResetEvent(false);     SynchronizationContext.Current.Post(_ => mre.Set(), null);     mre.WaitOne(); // warning: buggy }

Here, we’re creating a ManualResetEvent, a synchronization primitive that allows us to synchronously wait (block) until the primitive is set.  After creating it, we post back to the UI thread to set the event, and then we wait for it to be set.  But we’re waiting on the very thread that would go back to the message loop to pick up the posted message to do the set operation.  Deadlock.

The moral of this (longer than intended) story is that you should not block the UI thread.  Contrary to Nike’s recommendations, just don’t do it.  The new async language functionality makes it easy to asynchronous wait for your work to complete.  So, on your UI thread, instead of writing:

Task<string> s = LoadStringAsync();       textBox1.Text = s.Result; // BAD ON UI

you can write:

Task<string> s = LoadStringAsync();       textBox1.Text = await s; // GOOD ON UI

Or instead of writing:

Task t = DoWork();       t.Wait(); // BAD ON UI

you can write:

Task t = DoWork();       await t; // GOOD ON UI

This isn’t to say you should never block.  To the contrary, synchronously waiting for a task to complete can be a very effective mechanism, and can exhibit less overhead in many situations than the asynchronous counterpart.  There are also some contexts where asynchronously waiting can be dangerous. For these reasons and others, Task and Task<TResult> support both approaches, so you can have your cake and eat it too.  Just be cognizant of what you’re doing and when, and don’t block your UI thread.

(One final note: the Async CTP includes the TaskEx.ConfigureAwait method.  You can use this method to suppress the default behavior of marshaling back to the original synchronization context.  This could have been used, for example, in the LoadStringAsync method to prevent those awaits from needing to return to the UI thread.  This would not only have prevented the deadlock, it would have also resulted in better performance, because we now no longer need to force execution back to the UI thread, when nothing in that method actually needed to run on the UI thread.)

http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx

http://blogs.msdn.com/b/pfxteam/archive/2011/10/02/10219048.aspx

http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115163.aspx

转载于:https://www.cnblogs.com/oliverblogs/p/3583973.html

Await, and UI, and deadlocks! Oh my!相关推荐

  1. 使用await / async时,HttpClient.GetAsync(...)永远不会返回

    本文翻译自:HttpClient.GetAsync(-) never returns when using await/async Edit: This question looks like it ...

  2. 关于Async与Await的FAQ

    =============C#.Net 篇目录============== 环境:VS2012(尽管System.Threading.Tasks在.net4.0就引入,在.net4.5中为其增加了更丰 ...

  3. 关于async与await的FAQ 转

    (译)关于async与await的FAQ 传送门:异步编程系列目录-- 环境:VS2012(尽管System.Threading.Tasks在.net4.0就引入,在.net4.5中为其增加了更丰富的 ...

  4. 【转】2.1(译)关于async与await的FAQ

    传送门:异步编程系列目录-- 环境:VS2012(尽管System.Threading.Tasks在.net4.0就引入,在.net4.5中为其增加了更丰富的API及性能提升,另外关键字"a ...

  5. Async/Await FAQ

    From time to time, I receive questions from developers which highlight either a need for more inform ...

  6. 博客园客户端UAP开发随笔 -- App UI设计的三大纪律八项注意

    前言 每一个页面都是这个App的门面,尤其是主页面,看上去干净整洁清爽宜人容易操作,那么你的App就成功了一半.这也反映出了你这个开发团队的基本审美素质和设计理念.如果你不是一个团队,而是一个个人开发 ...

  7. php携程 线程,244,android线程与协程以及携程的使用方法

    1.线程就是线程, 2.协程本质就是一种线程框架,仅仅是针对Java中的Thread做了一次更友好的封装.让我们更方便的使用Java中的线程才是Kotlin-JVM中协程的真正目的. 本质上和Hand ...

  8. pdf 深入理解kotlin协程_协程初探

    Hello,各位朋友,小笨鸟我回来了! 近期学习了Kotlin协程相关的知识,感觉这块技术在项目中的可应用性很大,对项目的开发效率和维护成本有较大的提升.于是就考虑深入研究下相关概念和使用方式,并引入 ...

  9. 《Kotlin 程序设计》第十二章 Kotlin的多线程:协程(Coroutines)

    第十二章 Kotlin的多线程:协程(Coroutines) Kotlin 1.1 introduced coroutines, a new way of writing asynchronous, ...

最新文章

  1. Spring Cloud 2020.0.4 发布!
  2. 直播 | LiveVideoStack Meet杭州:后直播时代技术
  3. 实验:交换机生成树协议STP--功能验证
  4. catkin_make与gtest出现冲突的问题与解决
  5. Java中的DatagramPacket与DatagramSocket的初步
  6. linux安装spec编译器,RPM包制作之Spec文件参数详解
  7. 在移动端项目中使用vconsole
  8. Linux的网络管理命令使用总结
  9. 自建服务器打印机,关于Windows 2016 Server创建打印机服务器后对打印机设置权限的问题...
  10. maven 编译后无配置文件解决办法
  11. JS高程5.引用类型(6)Array类型的位置方法,迭代方法,归并方法
  12. Linux下conda 安装以后 activate无法使用
  13. [转] 2018年最新桌面CPU性能排行天梯图(含至强处理器)
  14. 宋体小二对应html中css,CSS字体中英文姓名对照表:如宋体对应SimSun
  15. 用计算机分析卫星云图 进行实时天气,气象卫星云图在地面气象观测中的运用...
  16. 从网上自学老男孩python全栈的笔记-经历
  17. C# 计算农历日期方法 2022
  18. pip安装指定清华镜像源
  19. 单射、满射、双射(一一映射)
  20. 亚马逊aws服务取消_Amazon S3 – AWS简单存储服务

热门文章

  1. 继承WebMvcConfigurer 和 WebMvcConfigurerAdapter类依然CORS报错? springboot 两种方式稳定解决跨域问题
  2. Tensorflow函数——tf.variable_scope()
  3. ESLint问题记录
  4. SpringSecurity安全验证中文乱码问题
  5. Kafka史上最详细原理总结 ----看完绝对不后悔
  6. LeetCode简单题之判断矩阵经轮转后是否一致
  7. 前后端分离必备工具:Swagger快速搞定(整合SpringBoot详细教程)
  8. SpringBoot配置文件YAML配置注入(详解)
  9. GPU编程和流式多处理器(二)
  10. 【网站汇总】论文相关