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

时间:2015-02-04 09:02:24

标签: c# unit-testing asynchronous task

我知道在这个网站和其他网站上有几个类似的问题,但是出于某种原因,这种做法的标准方式似乎不适用于我的情况。完成此要求的正常方法是使用TaskScheduler.FromCurrentSynchronizationContext()作为相关Task.Factory.StartNew overload中的TaskScheduler输入参数:

// Set uiTaskScheduler whilst on the UI thread
TaskScheduler uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
...
Task.Factory.StartNew(() => SomeMethodToRunAsynchronously(), 
    CancellationToken.None, TaskCreationOptions.None, uiTaskScheduler);

这似乎足以安排Task在UI线程上运行,但它似乎在我的情况下不起作用。就我而言,我有一个UiThreadManager类,其中包含RunAsynchronously方法:

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

这部分工作正常。我面临的问题是,在运行单元测试时,这个类被替换为MockUiThreadManager类(都实现了应用程序代码使用的IUiThreadManager接口),我似乎无法强迫方法在UI线程上运行:

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

MockUiThreadManager类在UI线程上设置了static UiTaskScheduler属性(如下所示),所以我假设通过上述方法的所有代码都会按预期在该线程上运行:< / p>

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

但是,在运行单元测试时,我注意到它在之前完成了正在测试的代码。因此,我在每个断点处的Visual Studio立即窗口中添加了一些断点并调用System.Threading.Thread.CurrentThread.ManagedThreadId,当然,当代码通过上述方法时,线程ID也发生了变化。

基本上,我正在寻找一种方法来基于Task的异步调用,用于在单元测试期间运行的RunAsynchronously方法,以确保方法实际上在UI线程上同步运行。有人看到我做错了什么,还是有其他建议?

更新&gt;&gt;&gt;

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

2 个答案:

答案 0 :(得分:4)

继续我的搜索后,我现在找到了我正在寻找的解决方案,只需几行代码即可实现,所以绝对不需要实现我自己的SynchronizationContext类。看看它有多简单,我很惊讶我之前没有找到它。在我运行单元测试时使用的MockUiThreadManager类中,我现在有了这段代码:

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

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

为了完整起见,Task.RunSynchronously Method还有一个覆盖TaskScheduler的覆盖,但在我的情况下这是不必要的,所以我不再需要MockUiThreadManager.UiTaskScheduler属性。< / p>

答案 1 :(得分:2)

new SynchronizationContext()返回一个新的默认SynchronizationObject,用于调度线程池(reference source)上的工作。

TaskScheduler.FromCurrentSynchronizationContext()返回使用SynchronizationContext.Current的任务计划程序,您只需使用SynchronizationContext.SetSynchronizationContext将其设置为线程池同步上下文。

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

通常,甚至无法在特定线程上安排工作,除非该线程具有某种类型的消息队列。这就是为什么你可以安排在UI线程上运行的工作。

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


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