同步等待另一个线程中的任务



我需要在一个特定的线程上做一些工作(出于所有意图和目的,我们可以说这是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();
}

我曾试图使RunTaskInMainThreadasync方法并使用await,但它一直导致我的程序挂起而不是产生结果。我确信我只是不正确地使用,但我不知道如何在这里实现它(奖金问题:我如何在这里使用await?)。

我在这里做错了什么是否有更好的方法来获得相同的结果?

你走错方向了。不是因为你不聪明,而是因为在你要移动的地方到处都是陷阱。

  1. TaskSchedulers不兼容async/await。它们是在async/await出现之前发明的,用于我们现在称为基于委托的任务(代表特定委托完成的任务),与之形成对比的是由async方法创建的任务,现在被称为promise-style任务(代表未来某个时候完成的承诺的任务)。

  2. SynchronizationContext类本身是无用的。它只对实现WindowsFormsSynchronizationContext或Stephen Cleary的AsyncContextSynchronizationContext等派生类的基类有用。遗憾的是,这个类没有像TaskScheduler那样定义为abstract,以防止程序员尝试按原样使用它。实现一个合适的SynchronizationContext派生类并非易事。

  3. 当一个线程通过调度程序(TaskSchedulerSynchronizationContext)用于调度工作时,该线程由调度程序拥有。不能让一个线程由两个调度器共享,也不能让一个调度器和某个方法在需要时随时使用该线程。这就是为什么当使用Application.Run方法在UI线程上启动消息循环时,该调用被阻塞的原因。在循环完成之前(在相关的windowsForm关闭之前),该调用之后的任何代码都不会执行。Stephen Cleary的AsyncContext也是如此。AsyncContext.Run呼叫阻塞

更多链接:

  • ParallelExtensionsExtras Tour - #7 -额外的任务调度器
  • ParallelExtensionsExtras源代码
  • 一个简单的SingleThreadTaskScheduler实现