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

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

概述我正在编写一个消耗资源的库,无论出于什么原因,API的设计方式是在不同的线程上引发事件,但是必须在主线程上完成API的调用. 假设我尝试使用的API定义为(我将省略事件定义): public sealed class DodgyService{ public void MethodThatHasToBeCalledOnTheMainThread() { ... }} 为了使用这个API 我正在编写一个消耗资源的库,无论出于什么原因,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    }}
总结

以上是内存溢出为你收集整理的如何使用C#中的TPL任务将工作编组到主线程上而不会导致死锁?全部内容,希望文章能够帮你解决如何使用C#中的TPL任务将工作编组到主线程上而不会导致死锁?所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1247320.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-07
下一篇 2022-06-07

发表评论

登录后才能评论

评论列表(0条)

保存