rod*_*elp 7 .net c# multithreading task-parallel-library
我正在编写一个消耗资源的库,无论出于什么原因,API的设计方式是在不同的线程上引发事件,但是必须在主线程上完成API的调用.
假设我尝试使用的API被定义为(我将省略事件定义):
public sealed class DodgyService
{
public void MethodThatHasToBeCalledOnTheMainThread() { ... }
}
Run Code Online (Sandbox Code Playgroud)
为了使用这个API,我在我的库中添加了一个名为Service(Yup,非常原始名称)的服务,它将创建一个新任务(当我指定一个已经创建的TaskScheduler时,它将在主线程上运行SynchronizationContext).
这是我的实现:
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);
}
Run Code Online (Sandbox Code Playgroud)
}
现在,如果我执行我的服务调用(在主线程上)并尝试在主线程上等待应用程序进入死锁,因为主线程将等待已安排在主线程上执行的任务.
如何在不阻塞整个过程的情况下将这些调用编组到主线程上?
在某些时候,我想在创建新任务之前执行主线程的检测,但我不想破解它.
对于任何感兴趣的人,我在这里得到了一个代码和一个展示问题的WPF应用程序的要点.
在btw上,库必须写在.net framework 4.0上
因为主线程将等待任务
这是一个有保障的僵局.任务无法在主线程上执行,直到它处于空闲状态,运行调度程序循环(也称为消息循环).正是该调度程序循环实现了使代码在特定线程上运行的魔力.然而,主线程不会空闲,它是"等待任务".所以任务无法完成,因为主线程不会空闲,主线程无法进入空闲状态,因为任务无法完成.死锁城市.
您必须重写代码,以便主线程不会等待.将等待调用后出现的任何代码移动到主线程上运行的另一个任务,就像ReallyLongCall()一样.
请注意,您似乎没有任何使用任务的里程数,您的代码段表明在工作线程上没有重要的代码.所以你不妨直接称它,也解决问题.
从您的示例程序中:
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);
}
Run Code Online (Sandbox Code Playgroud)
Task.WaitAll是阻塞调用,不能在主线程上执行阻塞调用,否则会导致死锁。Microsoft.Bcl.Async您可以做的(如果您使用的是 Visual Studio 2012 或更高版本)是使用支持async/await.Net 4.0 的NuGet 包。
添加包后将代码更改为
private async 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.
await TaskEx.WhenAll(list);
}
Run Code Online (Sandbox Code Playgroud)
并且您的程序将不再死锁(之后它也不会执行任何代码await TaskEx.WhenAll(list);,但这是因为该代码在关闭过程中运行,当您await将其放置在其他地方(例如单击事件)时,它会让关闭继续处理会看到更正常的行为)。
另一种选择是有第二个“主线程”并将工作分派给它。通常,当某些东西必须在“主”线程上运行时,实际上是在说它们需要在“ STA Windows 消息泵送该对象最初是在”线程上创建的。这是一个如何实现的示例(取自此处)
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
}
}
Run Code Online (Sandbox Code Playgroud)