如何保证一个Task在当前线程同步运行?

How to guarantee that a Task runs synchronously on the current thread?

我知道这个网站和其他网站上有几个类似的问题,但出于某种原因,标准的做法似乎不适用于我的情况。 正常方法来完成这个要求是在相关的Task.Factory.StartNew overload:

中使用TaskScheduler.FromCurrentSynchronizationContext()作为TaskScheduler输入参数
// Set uiTaskScheduler whilst on the UI thread
TaskScheduler uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
...
Task.Factory.StartNew(() => SomeMethodToRunAsynchronously(), 
    CancellationToken.None, TaskCreationOptions.None, uiTaskScheduler);

这似乎足以在 UI 线程上安排 Task 到 运行,但在我的情况下似乎不起作用。在我的例子中,我有一个 UiThreadManager class 其中有一个 RunAsynchronously 方法:

public Task RunAsynchronously(Action method)
{
    return Task.Run(method);
}

这部分工作得很好。我面临的问题是,当 运行ning 单元测试时,这个 class 被替换为 MockUiThreadManager class (两者都实现了一个 IUiThreadManager 接口通过应用程序代码),我似乎无法在 UI 线程上将 this 方法强制为 运行:

public Task RunAsynchronously(Action method)
{
    return Task.Factory.StartNew(() => method(), 
        CancellationToken.None, TaskCreationOptions.None, UiTaskScheduler);
}

MockUiThreadManager class 有一个 static UiTaskScheduler 属性 在 UI 线程上设置(如下所示),所以我假设所有代码通过上述方法将 运行 正如该线程上预期的那样:

SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
MockUiThreadManager.UiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();

但是,当 运行进行单元测试时,我注意到它 它正在测试的代码之前完成。于是我加了一些断点,在每个断点的Visual Studio Immediate Window中调用了System.Threading.Thread.CurrentThread.ManagedThreadId,果然,当代码通过上述方法时,线程ID发生了变化。

所以基本上,我正在寻找一种方法来 伪造 基于 TaskRunAsynchronously 方法的异步调用 运行 在单元测试期间,这将确保该方法实际上 运行 在 UI 线程上同步。有没有人看到我做错了什么,或者有任何其他建议?

更新>>>

好的,关于 UI 线程,我在这里使用了错误的术语。单元测试不会在 UI 线程上 运行 因为没有 UI... 但是,情况仍然相同。澄清一下,我只需要我的测试 运行 同步应用程序代码并在它启动的 main 单线程上。问题是当应用程序 运行ning 时,有很多基于 Task 的异步代码,这需要通过 MockUiThreadManager class 同步测试。

new SynchronizationContext() returns 一个新的默认 SynchronizationObject,它在线程池 (reference source) 上安排工作。

TaskScheduler.FromCurrentSynchronizationContext() returns 使用 SynchronizationContext.Current 的任务调度程序,您刚刚使用 SynchronizationContext.SetSynchronizationContext.

将其设置为线程池同步上下文

这意味着在该调度程序上调度任务将使用线程池来执行它们,而不是特定线程。

通常甚至不可能在特定线程上安排工作,除非该线程有某种消息队列。这就是为什么您可以在 UI 线程上将工作安排到 运行。

不知道你用的是哪个单元测试框架。它可能有一个 UI 来显示测试结果,但这并不意味着该线程上的测试是 运行。


除了自己写 SynchronizationContext class,我不确定如何解决这个问题。您想要在 UI 线程上对必须 运行 的内容进行单元测试也感觉有点奇怪,因为在我看来 UI 本身不太适合进行单元测试.如果您使用 ViewModelController 之类的东西,那么您当然可以对它们进行单元测试,但无论您使用何种同步上下文,它们都应该可以正常工作。

继续搜索后,我现在找到了我正在寻找的解决方案,只需几行代码就可以实现,所以绝对不需要自己实现 SynchronizationContext class.看着它是多么简单,我很惊讶我没有早点找到它。在 MockUiThreadManager class 运行ning 单元测试时使用的 MockUiThreadManager class 中,我现在有以下代码:

public Task RunAsynchronously(Action method)
{
    Task task = new Task(method);
    task.RunSynchronously();
    return task;
}

我可以确认它按照它说的做,运行method 函数在测试 运行 所在的同一个线程上同步运行。

为了完整起见,Task.RunSynchronously Method 也有一个覆盖 TaskScheduler,但这对我来说是不必要的,所以我不需要我的 MockUiThreadManager.UiTaskScheduler 属性 了。