C# 等待异步WCF调用不返回UI线程和/或阻止UI线程
我已经将一个.NET 4.0 WinForms程序升级到.NET 4.5.1,希望在异步WCF调用上使用新的Wait,以防止在等待数据时冻结UI(最初的程序编写得很快,所以我希望旧的同步WCF调用可以异步,使用新的Wait功能对现有代码进行最小的更改) 据我所知,await应该返回到UI线程,而不需要额外编码,但出于某种原因,它不适合我,因此下面将给出跨线程异常:C# 等待异步WCF调用不返回UI线程和/或阻止UI线程,c#,wcf,asynchronous,ui-thread,async-await,C#,Wcf,Asynchronous,Ui Thread,Async Await,我已经将一个.NET 4.0 WinForms程序升级到.NET 4.5.1,希望在异步WCF调用上使用新的Wait,以防止在等待数据时冻结UI(最初的程序编写得很快,所以我希望旧的同步WCF调用可以异步,使用新的Wait功能对现有代码进行最小的更改) 据我所知,await应该返回到UI线程,而不需要额外编码,但出于某种原因,它不适合我,因此下面将给出跨线程异常: private async void button_Click(object sender, EventArgs e) {
private async void button_Click(object sender, EventArgs e)
{
using (MyService.MyWCFClient myClient = MyServiceConnectFactory.GetForUser())
{
var list=await myClient.GetListAsync();
dataGrid.DataSource=list; // fails if not on UI thread
}
}
在这篇文章之后,我制作了一个定制的等待器,这样我就可以发布一个等待此
来返回UI线程,这解决了异常,但随后我发现我的UI仍然冻结,尽管使用了Visual Studio 2013为我的WCF服务生成的异步任务
现在这个程序实际上是一个Hydra VisualPlugin,运行在一个旧的Delphi应用程序中,所以如果有什么事情会把事情搞砸,很可能会发生。。。但是,有没有人对等待异步WCF不返回UI线程或挂起UI线程有什么经验?
也许从4.0升级到4.5.1会让程序错过一些神奇的参考
现在,虽然我想理解为什么wait不能像广告中所宣传的那样工作,但我最终还是制定了自己的解决方案:一个自定义的wait程序,它强制任务在后台线程中运行,并强制继续返回UI线程。
与.ConfigureAwait(false)
类似,我为TAK编写了一个.runWithReturnToIthread(this)
扩展,如下所示:
public static RunWithReturnToUIThreadAwaiter<T> RunWithReturnToUIThread<T>(this Task<T> task, Control control)
{
return new RunWithReturnToUIThreadAwaiter<T>(task, control);
}
public class RunWithReturnToUIThreadAwaiter<T> : INotifyCompletion
{
private readonly Control m_control;
private readonly Task<T> m_task;
private T m_result;
private bool m_hasResult=false;
private ExceptionDispatchInfo m_ex=null; // Exception
public RunWithReturnToUIThreadAwaiter(Task<T> task, Control control)
{
if (task == null) throw new ArgumentNullException("task");
if (control == null) throw new ArgumentNullException("control");
m_task = task;
m_control = control;
}
public RunWithReturnToUIThreadAwaiter<T> GetAwaiter() { return this; }
public bool IsCompleted
{
get
{
return !m_control.InvokeRequired && m_task.IsCompleted; // never skip the OnCompleted event if invoke is required to get back on UI thread
}
}
public void OnCompleted(Action continuation)
{
// note to self: OnCompleted is not an event - it is called to specify WHAT should be continued with ONCE the result is ready, so this would be the place to launch stuff async that ends with doing "continuation":
Task.Run(async () =>
{
try
{
m_result = await m_task.ConfigureAwait(false); // await doing the actual work
m_hasResult = true;
}
catch (Exception ex)
{
m_ex = ExceptionDispatchInfo.Capture(ex); // remember exception
}
finally
{
m_control.BeginInvoke(continuation); // give control back to continue on UI thread even if ended in exception
}
});
}
public T GetResult()
{
if (m_ex == null)
{
if (m_hasResult)
return m_result;
else
return m_task.Result; // if IsCompleted returned true then OnCompleted was never run, so get the result here
}
else
{ // if ended in exception, rethrow it
m_ex.Throw();
throw m_ex.SourceException; // just to avoid compiler warning - the above does the work
}
}
}
public static runwithreturntouithreadwaiter RunWithReturnToUIThread(此任务,控制)
{
使用ReturnToIthReadWaiter返回新运行(任务、控制);
}
使用ReturnToIthReadWaiter运行的公共类:INotifyCompletion
{
私有只读控件m_控件;
私有只读任务m_任务;
私人信托结果;
private bool m_hassresult=false;
私有异常DispatchInfo m_ex=null;//异常
public runwithreturntoithreadawaiter(任务任务、控制)
{
如果(task==null)抛出新的ArgumentNullException(“task”);
如果(control==null)抛出新的ArgumentNullException(“control”);
m_任务=任务;
m_控制=控制;
}
public runwithreturntoithreadawaiter GetAwaiter(){returnthis;}
公共图书馆完工了
{
得到
{
return!m_control.invokererequired&&m_task.IsCompleted;//如果需要调用以返回UI线程,请不要跳过OnCompleted事件
}
}
未完成公共作废(行动继续)
{
//self注意:OnCompleted不是一个事件-调用它是为了指定在结果准备好后应该继续什么,因此这将是启动stuff async的地方,以执行“continuation”结束:
Task.Run(异步()=>
{
尝试
{
m_result=await m_task.ConfigureAwait(false);//等待执行实际工作
m_hasResult=真;
}
捕获(例外情况除外)
{
m_ex=ExceptionDispatchInfo.Capture(ex);//记住异常
}
最后
{
m_control.BeginInvoke(continuation);//返回控件以在UI线程上继续,即使以异常结束
}
});
}
公共T GetResult()
{
如果(m_ex==null)
{
如果(m_hasResult)
返回m_结果;
其他的
return m_task.Result;//如果IsCompleted返回true,那么OnCompleted从未运行过,因此在此处获取结果
}
其他的
{//如果以exception结尾,请重新显示它
m_ex.Throw();
抛出m_ex.SourceException;//只是为了避免编译器警告-以上方法可以完成此工作
}
}
}
在上面的例子中,我不确定是否需要这样的异常处理,或者Task.Run是否真的需要在其代码中使用async和Wait,或者多层任务是否会出现问题(我基本上绕过了封装任务自己的返回方法,因为它在我的WCF服务程序中没有正确返回)
关于上述解决方法的效率,或者是什么导致问题的产生,有何评论/想法
现在这个程序实际上是一个Hydra VisualPlugin,运行在一个旧的Delphi应用程序中
这可能就是问题所在。正如我在中所解释的,当您await
一个任务
并且该任务不完整时,await
操作符默认会捕获一个“当前上下文”,然后在该上下文中恢复async
方法。“当前上下文”是SynchronizationContext.current
,除非它是null
,在这种情况下它是TaskScheduler.current
因此,正常的“返回UI线程”行为是wait
捕获UI同步上下文的结果-对于WinForms,是WinFormsSynchronizationContext
在普通WinForms应用程序中,首次创建控件时,SynchronizationContext.Current
被设置为WinFormsSynchronizationContext
。不幸的是,这种情况并不总是发生在插件体系结构中(我在MicrosoftOffice插件上看到过类似的行为)。我怀疑当代码等待时,SynchronizationContext.Current
为null
和TaskScheduler.Current
为TaskScheduler.Default
(即线程池任务调度器)
因此,我要尝试的第一件事是创建一个控件
:
void EnsureProperSynchronizationContext()
{
if (SynchronizationContext.Current == null)
var _ = new Control();
}
希望您只需在第一次调用插件时执行一次。但是,您可能必须在主机可以调用的所有方法开始时执行此操作
如果这不起作用,您可以创建自己的SynchronizationContext
,但如果可以,最好使用WinForms one。自定义等待器也是可能的(如果您选择该路线,包装TaskAwaiter