微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何使用C#中的TPL任务将工作编组到主线程上而不会导致死锁?

我正在编写一个消耗资源的库,无论出于什么原因,API的设计方式是在不同的线程上引发事件,但是必须在主线程上完成API的调用.

假设我尝试使用的API定义为(我将省略事件定义):

public sealed class DodgyService
{
    public void MethodThatHasToBeCalledOnTheMainThread() { ... }
}

为了使用这个API,我在我的库中添加一个名为Service(Yup,非常原始名称)的服务,它将创建一个新任务(当我指定一个从SynchronizationContext创建的TaskScheduler时,它将在主线程上运行).

这是我的实施:

public class Service
{
  private readonly TaskFactory _taskFactory;
  private readonly TaskScheduler _mainThreadScheduler;

  public Service(TaskFactory taskFactory,TaskScheduler mainThreadScheduler)
  {
      _taskFactory = taskFactory;
      _mainThreadScheduler = mainThreadScheduler;
  }

  // Assume this method can be called from any thread.
  // In this sample is called by the main thread but most of the time
  // the caller will be running on a background thread.
  public Task ExecuteAsync(string taskName)
  {
      return _taskFactory.StartNew(
          () => ReallyLongCallThatForWhateverStupidReasonHasToBeCalledOnMainThread(taskName),new CancellationToken(false),TaskCreationoptions.None,_mainThreadScheduler)
          .ContinueWith(task => Trace.Traceinformation("ExecuteAsync has completed on \"{0}\"...",taskName));
  }

  private void ReallyLongCallThatForWhateverStupidReasonHasToBeCalledOnMainThread(string taskName)
  {
      Trace.Traceinformation("Starting \"{0}\" really long call...",taskName);
      new DodgyService().MethodThatHasToBeCalledOnTheMainThread();
      Trace.Traceinformation("Finished \"{0}\" really long call...",taskName);
  }

}

现在,如果我执行我的服务调用(在主线程上)并尝试在主线程上等待应用程序进入死锁,因为主线程将等待已安排在主线程上执行的任务.

如何在不阻塞整个过程的情况下将这些调用编组到主线程上?

在某些时候,我想在创建新任务之前执行主线程的检测,但我不想破解它.

对于任何感兴趣的人,我得到了一个带有代码的虚拟here一个展示该问题的WPF应用程序.

在btw上,库必须写在.net framework 4.0上

编辑!
我按照0700提供的建议按照here提供的方式解决了我的问题

解决方法

从您的示例程序:
private void HandleClosed(object sender,EventArgs e)
  {
      var list = new[]
      {
          _service.ExecuteAsync("first task"),_service.ExecuteAsync("second task"),_service.ExecuteAsync("third task")
      };

      //uncommenting this line blocks all three prevIoUs activities as expected
      //as it drives the current main thread to wait for other tasks waiting to be executed by the main thread.

      //Task.WaitAll(list);
  }

Task.WaitAll是一个阻塞调用,你不能在主线程上执行阻塞调用,否则你将导致死锁.你可以做什么(如果你使用的是Visual Studio 2012或更新版本)是使用NuGet软件包Microsoft.Bcl.Async,它为.Net 4.0提供异步/等待支持.

添加包后,将代码更改为

private async void HandleClosed(object sender,EventArgs e)
{
    var list = new[]
  {
      _service.ExecuteAsync("first task"),_service.ExecuteAsync("third task")
  };

    //uncommenting this line blocks all three prevIoUs activities as expected
    //as it drives the current main thread to wait for other tasks waiting to be executed by the main thread.

    await TaskEx.WhenAll(list);
}

并且你的程序将不再死锁(在等待TaskEx.WhenAll(list)之后它也不会执行任何代码;但这是因为此代码关闭过程中运行,当你等待它时,让关闭继续处理,如果它被放置在其他地方,就像点击事件,你会看到更多的正常行为).

另一种选择是拥有第二个“主线程”并将工作分配给它.通常当必须在“主”线程上运行某些东西实际上是说它们需要在“一个STA的Windows消息上运行,该对象最初是在”线程上创建的.这是一个如何实现的例子(取自here)

private void runbrowserThread(Uri url) {
    var th = new Thread(() => {
        var br = new Webbrowser();
        br.DocumentCompleted += browser_DocumentCompleted;
        br.Navigate(url);
        Application.Run();
    });
    th.SetApartmentState(ApartmentState.STA);
    th.Start();
}

void browser_DocumentCompleted(object sender,WebbrowserDocumentCompletedEventArgs e) {
    var br = sender as Webbrowser;
    if (br.Url == e.Url) {
        Console.WriteLine("Natigated to {0}",e.Url);
        Application.ExitThread();   // Stops the thread
    }
}

原文地址:https://www.jb51.cc/csharp/101035.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐