繁体   English   中英

等待异步WCF调用不返回到UI线程和/或阻止UI线程

[英]await of async WCF call not returning to UI thread and/or blocking UI thread

我已将.NET 4.0 WinForms程序升级到.NET 4.5.1,希望在异步WCF调用上使用新的等待状态,以防止在等待数据时冻结UI(原件很快写好了,所以我希望旧的使用新的等待功能,可以在不更改现有代码的情况下使同步WCF调用异步进行)。

据我了解,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
    }
}

在文章等待任何内容之后,我做了一个自定义等待者,因此我可以发出await this以返回到UI线程,从而解决了异常,但是后来我发现我的UI仍然被冻结,尽管使用了Visual Studio 2013生成的异步任务我的WCF服务。

现在,该程序实际上是在旧的Delphi应用程序中运行的Hydra VisualPlugin,因此,如果有什么可能使事情搞砸,则可能会发生...但是任何人都没有任何经验,可以确切地等待异步WCF而不返回UI线程或挂起UI线程? 也许从4.0升级到4.5.1会使程序错过做魔术的一些参考?

现在,尽管我想了解为什么await不能像宣传的那样工作,但我还是做出了自己的解决方法:一个自定义的awaiter,它强制任务在后台线程中运行,并强制继续执行返回到UI线程。 .RunWithReturnToUIThread(this) .ConfigureAwait(false)类似,我为.RunWithReturnToUIThread(this)编写了.RunWithReturnToUIThread(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
    }
  }
}

现在,在上面的内容中,我不确定是否需要这样的异常处理,或者Task.Run是否确实需要在其代码中使用async和await,或者不确定多层Tasks是否会产生问题(我基本上绕过了封装的Task自己的返回方法-因为在我的WCF服务程序中它没有正确返回)。

关于上述解决方法效率的任何意见/想法,或者是什么导致问题开始?

现在,该程序实际上是在旧的Delphi应用程序中运行的Hydra VisualPlugin

那可能是问题所在。 正如我在async介绍博客文章中所解释的那样,当您await一个Task且该任务不完整时,默认情况下, await运算符将捕获“当前上下文”,然后在该上下文中恢复async方法。 除非为null ,否则“当前上下文”为SynchronizationContext.Current ,在这种情况下为TaskScheduler.Current

因此,正常的“返回UI线程”行为是await捕获UI同步上下文的结果-在WinForms的情况下是WinFormsSynchronizationContext

在普通的WinForms应用程序中,第一次创建Control时, SynchronizationContext.Current设置为WinFormsSynchronizationContext 不幸的是,这并非总是在插件体系结构中发生(我已经在Microsoft Office插件上看到类似的行为)。 我怀疑当您的代码等待时, SynchronizationContext.CurrentnullTaskScheduler.CurrentTaskScheduler.Default (即线程池任务计划程序)。

因此,我首先尝试创建一个Control

void EnsureProperSynchronizationContext()
{
  if (SynchronizationContext.Current == null)
    var _ = new Control();
}

希望您第一次调用插件时只需执行一次。 但是您可能必须在主机可以调用的所有方法的开始时执行此操作。

如果这不起作用,则可以创建自己的SynchronizationContext ,但是如果可以的话,最好使用WinForms。 也可以使用自定义的等待者(如果走那条路线,包装TaskAwaiter<T>Task<T>容易),但是自定义的等待者的缺点是每次await都必须进行。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM