我需要在一个特定的线程上做一些工作(出于所有意图和目的,我们可以说这是UI线程),但是请求完成工作的方法可能在不同的线程中执行,也可能不执行。我对多线程编程完全陌生,但得出的结论是,正确的方法是使用TaskScheduler。
在玩弄了一段时间的自定义实现后,我发现FromCurrentSynchronizationContext。这个似乎正是我所需要的,为我节省了很多麻烦(著名的遗言等等)。我的问题归结为我是否忽略了任何会让我陷入麻烦的事情,或者我可能把问题复杂化了。下面是我正在做的:
TaskScheduler
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Internals
{
internal static class MainThreadTaskScheduler
{
private static readonly object taskSchedulerLock = new();
private static readonly Thread taskSchedulerThread;
private static readonly TaskScheduler taskScheduler;
static MainThreadTaskScheduler()
{
lock (taskSchedulerLock)
{
// despite calling it the "main thread", we don't really care which thread
// this is initialized with, we just need to always use the same one for
// executing the scheduled tasks
taskSchedulerThread = Thread.CurrentThread;
if (SynchronizationContext.Current is null)
{
// in implementation this may be null, a default context works
SynchronizationContext.SetSynchronizationContext(new());
}
taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
}
}
public static Task Schedule(Action action)
{
lock (taskSchedulerLock)
{
if (Thread.CurrentThread == taskSchedulerThread)
{
// if we are already on the main thread, just run the delegate
action();
return Task.CompletedTask;
}
return Task.Factory.StartNew(action, CancellationToken.None,
TaskCreationOptions.None, taskScheduler);
}
}
public static Task<TResult> Schedule<TResult>(Func<TResult> func)
{
lock (taskSchedulerLock)
{
if (Thread.CurrentThread == taskSchedulerThread)
{
// if we are already on the main thread, just run the delegate
return Task.FromResult(func());
}
return Task.Factory.StartNew(func, CancellationToken.None,
TaskCreationOptions.None, taskScheduler);
}
}
}
}
使用
// ...elsewhere...
public static bool RunTaskInMainThread()
{
// we need to synchronously return the result from the main thread regardless of
// which thread we are currently executing in
return MainThreadTaskScheduler.Schedule(() => SomeMethod()).GetAwaiter().GetResult();
}
我曾试图使RunTaskInMainThread
和async
方法并使用await
,但它一直导致我的程序挂起而不是产生结果。我确信我只是不正确地使用,但我不知道如何在这里实现它(奖金问题:我如何在这里使用await
?)。
我在这里做错了什么吗是否有更好的方法来获得相同的结果?
你走错方向了。不是因为你不聪明,而是因为在你要移动的地方到处都是陷阱。
-
TaskScheduler
s不兼容async/await。它们是在async/await出现之前发明的,用于我们现在称为基于委托的任务(代表特定委托完成的任务),与之形成对比的是由async方法创建的任务,现在被称为promise-style任务(代表未来某个时候完成的承诺的任务)。 -
SynchronizationContext
类本身是无用的。它只对实现WindowsFormsSynchronizationContext
或Stephen Cleary的AsyncContextSynchronizationContext
等派生类的基类有用。遗憾的是,这个类没有像TaskScheduler
那样定义为abstract
,以防止程序员尝试按原样使用它。实现一个合适的SynchronizationContext
派生类并非易事。 -
当一个线程通过调度程序(
TaskScheduler
或SynchronizationContext
)用于调度工作时,该线程由调度程序拥有。不能让一个线程由两个调度器共享,也不能让一个调度器和某个方法在需要时随时使用该线程。这就是为什么当使用Application.Run
方法在UI线程上启动消息循环时,该调用被阻塞的原因。在循环完成之前(在相关的windowsForm
关闭之前),该调用之后的任何代码都不会执行。Stephen Cleary的AsyncContext
也是如此。AsyncContext.Run
呼叫阻塞
更多链接:
- ParallelExtensionsExtras Tour - #7 -额外的任务调度器
- ParallelExtensionsExtras源代码 一个简单的SingleThreadTaskScheduler实现