C# 等待异步WCF调用不返回UI线程和/或阻止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) {

我已经将一个.NET 4.0 WinForms程序升级到.NET 4.5.1,希望在异步WCF调用上使用新的Wait,以防止在等待数据时冻结UI(最初的程序编写得很快,所以我希望旧的同步WCF调用可以异步,使用新的Wait功能对现有代码进行最小的更改)

据我所知,await应该返回到UI线程,而不需要额外编码,但出于某种原因,它不适合我,因此下面将给出跨线程异常:

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