接上篇
上篇勇哥写的演示程序中关于SynchronizationContext的post方法的示例,并没有实际意义。
再来一个实例:
程序三个按钮,我们想按下按钮后访问网页,把其内容赋值给按钮的Text属性
第一按钮借助TaskScheduler的方法FromCurrentSynchronizationContext
该方法会创建一个SynchronizationContextTaskScheduler实例并返回,以便在原始的SynchronizationContext.Current上的Post方法对任务进行排队执行。
注意Debug.WriteLine(1) 和 (2) 输出两个调试信息, 显示托管的线程ID, 你会发现UI线程ID、异步代码中的线程ID是相同的。证明了异常代码被妥托到了UI线程上执行了。
第二个铵钮只接用SynchronizationContext的Current属性。它用于获取当前线程的同步上下文。
如果我正在编写一个库,想要停下来做一些工作,然后将委托排队送回“原始上下文”继续执行, 那么我只需要获取他们的SynchronizationContext,存下来。 当完成工作后,在该上下文上调用Post去传递我想要调用的委托即可。 我不需在WinForm中知道要获取一个控件并调用BeginInvoke, 不需要在WPF中知道要对Dispatcher进行BeginInvoke, 也不需要在xunit中知道要以某种方式获取其上下文并排队, 我只需要获取当前的SynchronizationContext并在以后使用它就可以了。 为此,借助SynchronizationContext提供的Current属性
第三个按钮使用.net4.0的 async/await的异步机制实现了上面两个按钮的功能。
这是一种非常自然的写法,把异步代码写成了同步代码的形式。
就这样,成功在UI线程上设置了按钮的内容,与上面两个按钮实现的手动版本一样, await Task默认会关注SynchronizationContext.Current和TaskScheduler.Current两个参数。 当你在C#中使用await时,编译器会进行代码转换来向“可等待者”(这里为Task) 索要(通过调用GetAwaiter)“awaiter”(这里为TaskAwaiter<string>)。 该awaiter负责挂接回调(通常称为“继续(continuation)”), 当等待的对象完成时,该回调将被封送到状态机, 并使用在注册回调时捕获的上下文或调度程序来执行此回调。 尽管与实际代码不完全相同(实际代码还进行了其他优化和调整), 但大体上是这样的: object scheduler = SynchronizationContext.Current; if (scheduler is null && TaskScheduler.Current != TaskScheduler.Default) { scheduler = TaskScheduler.Current; } 说人话就是,它先检查有没有设置当前SynchronizationContext,如果没有, 则再判断当前调度程序是否为默认的TaskScheduler。 如果不是,那么当准备好调用回调时,会使用该调度程序执行回调; 否则,通常会作为完成已等待任务的操作的一部分来执行回调 (译注:这个“否则”我也没看懂,我的理解是如果有当前上下文, 则使用当前上下文执行回调;如果当前上下文为空, 且使用的是默认调度程序ThreadPoolTaskScheduler,则会启用线程池线程执行回调)。
代码:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Drawing; using System.Linq; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace WindowsFormsApplication7 { public partial class Form1 : Form { private static readonly HttpClient s_httpClient = new HttpClient(); public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { /* 假设有一个UI App,它有一个按钮。当点击按钮后,会从网上下载一些文本并将其设置为按钮的内容。 我们应当只在UI线程中访问该按钮,因此当我们成功下载新的文本后, 我们需要从拥有按钮控制权的的线程中将其设置为按钮的内容。 */ Debug.WriteLine($"(1){Environment.CurrentManagedThreadId}"); s_httpClient.GetStringAsync("https://www.baidu.com/").ContinueWith(downloadTask => { downloadBtn.Text = downloadTask.Result; Debug.WriteLine($"(2){Environment.CurrentManagedThreadId}"); }, TaskScheduler.FromCurrentSynchronizationContext()); } private void button1_Click_1(object sender, EventArgs e) { /* 或直接使用SynchronizationContext: 不过,这两种方式都需要显式指定回调,更好的方式是通过async/await自然地进行编码, 见第三个按钮的代码 */ Debug.WriteLine($"(1){Environment.CurrentManagedThreadId}"); SynchronizationContext sc = SynchronizationContext.Current; s_httpClient.GetStringAsync("https://www.baidu.com/").ContinueWith(downloadTask => { sc.Post(delegate { downloadBtn2.Text = downloadTask.Result; Debug.WriteLine($"(2){Environment.CurrentManagedThreadId}"); }, null); }); } private async void downloadBtn3_Click(object sender, EventArgs e) { string text = await s_httpClient.GetStringAsync("https://www.baidu.com/"); downloadBtn3.Text = text; } } }
---------------------
作者:hackpig
来源:www.skcircle.com
版权声明:本文为博主原创文章,转载请附上博文链接!

