【问题标题】:How to guarantee that a Task runs synchronously on the current thread?如何保证一个Task在当前线程上同步运行?
【发布时间】:2015-02-05 18:18:22
【问题描述】:

我知道这个网站和其他网站上有几个类似的问题,但由于某种原因,这样做的标准方法似乎不适用于我的情况。实现此要求的正常方法是在相关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);

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

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

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

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

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

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

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

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

更新>>>

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

【问题讨论】:

  • 这对我来说似乎很反直觉。通常,当您运行 Task.Factory.StartNew 时,您将在 UI 线程上。
  • UiSynchronizationContext 传递给StartNew 的值是多少?向我们展示您在测试中如何调用Task.Factory.StartNew
  • 通常,当您运行 Task.Factory.StartNew 时,您将在 UI 线程上...这完全不正确,因为这完全取决于当前上下文。 @YuvalItzchakov,你的意思是UiTaskScheduler?我没有将它传递给StartNew 方法......正如我在问题中所说,我在 UI 线程上初始化它......在 Intellisense 中,它只是说Id=1,所以它绝对不是null。另外,我在测试中没有调用StartNew,而是调用了上面显示的(IMockUiThreadManager)RunAsynchronously方法。
  • @Sheridan 您是否考虑过简单地执行Action 并使用Task.FromResult 将其包装在Task 中?
  • @Sheridan:共享SynchronizationContext 对象所涉及的技术不正是您要寻找的吗? IE。在IUiThreadManagerSynchronizationContext 内调用测试?

标签: c# unit-testing asynchronous task


【解决方案1】:

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

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

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

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

【讨论】:

    【解决方案2】:

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

    TaskScheduler.FromCurrentSynchronizationContext() 返回一个使用 SynchronizationContext.Current 的任务调度程序,您刚刚使用 SynchronizationContext.SetSynchronizationContext 将其设置为线程池同步上下文。

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

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

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


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

    【讨论】:

    • 对不起@Dirk,我不应该使用 UI 线程这个词...我的意思是主线程...我只需要测试来同步运行所有内容。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多