Max*_*aga 3 .net c# asynchronous async-await
我不知道我做错了什么或者我在Async库中发现了一个错误,但在使用continueWith()回到Synchronized上下文后运行一些异步代码时我遇到了一个问题.
更新:代码现在运行
using System;
using System.ComponentModel;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
internal static class Program
{
[STAThread]
private static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
MainFrameController controller = new MainFrameController(this);
//First async call without continueWith
controller.DoWork();
//Second async call with continueWith
controller.DoAsyncWork();
}
public void Callback(Task<HttpResponseMessage> task)
{
Console.Write(task.Result); //IT WORKS
MainFrameController controller =
new MainFrameController(this);
//third async call
controller.DoWork(); //IT WILL DEADLOCK, since ConfigureAwait(false) in HttpClient DOESN'T change context
}
}
internal class MainFrameController
{
private readonly Form1 form;
public MainFrameController(Form1 form)
{
this.form = form;
}
public void DoAsyncWork()
{
Task<HttpResponseMessage> task = Task<HttpResponseMessage>.Factory.StartNew(() => DoWork());
CallbackWithAsyncResult(task);
}
private void CallbackWithAsyncResult(Task<HttpResponseMessage> asyncPrerequisiteCheck)
{
asyncPrerequisiteCheck.ContinueWith(task =>
form.Callback(task),
TaskScheduler.FromCurrentSynchronizationContext());
}
public HttpResponseMessage DoWork()
{
MyHttpClient myClient = new MyHttpClient();
return myClient.RunAsyncGet().Result;
}
}
internal class MyHttpClient
{
public async Task<HttpResponseMessage> RunAsyncGet()
{
HttpClient client = new HttpClient();
return await client.GetAsync("https://www.google.no").ConfigureAwait(false);
}
}
partial class Form1
{
private IContainer components;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Text = "Form1";
}
#endregion
}
}
Run Code Online (Sandbox Code Playgroud)
代码中的主要问题是StartNew和ContinueWith.正如我在博客中描述的那样ContinueWith,StartNew危险的原因与危险相同.
总结:StartNew并且ContinueWith只应在您执行基于动态任务的并行性(此代码不是)时使用.
在实际的问题是,HttpClient.GetAsync不使用(相当于)ConfigureAwait(false); 它使用ContinueWith其默认的调度参数(这就是TaskScheduler.Current,不是 TaskScheduler.Default).
要更详细地解释......
对默认调度StartNew和ContinueWith是不是 TaskScheduler.Default(线程池); 它是TaskScheduler.Current(当前的任务调度程序).所以,在你的代码,DoAsyncWork因为它目前并不能总是执行DoWork的线程池.
第一次DoAsyncWork被调用,它将在UI线程上调用,但没有当前TaskScheduler.在这种情况下,与线程池TaskScheduler.Current相同TaskScheduler.Default,并DoWork在线程池上调用.
然后,使用在UI线程上运行它的CallbackWithAsyncResult调用.因此,在调用时,它会在UI线程上使用当前(UI任务调度程序)调用.在这种情况下,是UI任务调度程序,并最终调用UI线程.Form1.CallbackTaskSchedulerForm1.CallbackDoAsyncWorkTaskSchedulerTaskScheduler.CurrentDoAsyncWorkDoWork
因此,您应该始终指定TaskScheduler何时调用StartNew或ContinueWith.
所以,这是一个问题.但它实际上并没有导致您看到的死锁,因为ConfigureAwait(false) 应该允许此代码阻止UI而不是死锁.
它陷入僵局,因为微软犯了同样的错误.在这里查看第198行:( GetContentAsync被调用者GetAsync)使用ContinueWith而不指定调度程序.因此,它TaskScheduler.Current从您的代码中获取,并且在它可以在该调度程序(即UI线程)上运行之前不会完成其任务,从而导致经典的死锁.
你没有办法解决这个问题HttpClient.GetAsync(显然).你只需要解决它,最简单的方法就是避免使用TaskScheduler.Current.永远,如果可以的话.
以下是异步代码的一些一般准则:
StartNew.请Task.Run改用.ContinueWith.请await改用.Result.请await改用.如果我们只进行最小的更改(替换为StartNewwith Run和ContinueWithwith await),则DoAsyncWork 始终DoWork在线程池上执行,并避免死锁(因为直接await使用SynchronizationContext而不是a TaskScheduler):
public void DoAsyncWork()
{
Task<HttpResponseMessage> task = Task.Run(() => DoWork());
CallbackWithAsyncResult(task);
}
private async void CallbackWithAsyncResult(Task<HttpResponseMessage> asyncPrerequisiteCheck)
{
try
{
await asyncPrerequisiteCheck;
}
finally
{
form.Callback(asyncPrerequisiteCheck);
}
}
Run Code Online (Sandbox Code Playgroud)
但是,拥有基于任务的异步的回调场景总是值得怀疑的,因为任务本身具有回调功能.看起来你正在尝试进行某种异步初始化; 我有一篇关于异步构造的博客文章,展示了一些可能的方法.
即使它async void用于初始化,即使是像这样真正基本的东西也会比回调(再次,IMO)更好的设计:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
MainFrameController controller = new MainFrameController();
controller.DoWork();
Callback(controller.DoAsyncWork());
}
private async void Callback(Task<HttpResponseMessage> task)
{
await task;
Console.Write(task.Result);
MainFrameController controller = new MainFrameController();
controller.DoWork();
}
}
internal class MainFrameController
{
public Task<HttpResponseMessage> DoAsyncWork()
{
return Task.Run(() => DoWork());
}
public HttpResponseMessage DoWork()
{
MyHttpClient myClient = new MyHttpClient();
var task = myClient.RunAsyncGet();
return task.Result;
}
}
Run Code Online (Sandbox Code Playgroud)
当然,这里还有其他设计问题:即,DoWork在自然异步操作DoAsyncWork上阻塞,并在自然异步操作上阻塞线程池线程.因此,在Form1调用时DoAsyncWork,它正在等待在异步操作中被阻塞的线程池任务.Async-over-sync-over-async,即.您也可以从我的礼仪博客系列中Task.Run受益.
| 归档时间: |
|
| 查看次数: |
2277 次 |
| 最近记录: |