简体   繁体   English

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

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

I'm aware that there are several similar questions on this and other websites, but the standard way of doing this doesn't appear to work in my situation for some reason. 我知道在这个网站和其他网站上有几个类似的问题,但是由于某种原因,这种做法的标准方式似乎不适用于我的情况。 The normal way to accomplish this requirement is to use TaskScheduler.FromCurrentSynchronizationContext() as the TaskScheduler input parameter in the relevant Task.Factory.StartNew overload : 完成此要求的常规方法是使用TaskScheduler.FromCurrentSynchronizationContext()作为相关Task.Factory.StartNew重载中TaskScheduler输入参数:

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

It appears that this is enough to schedule a Task to run on the UI thread, but it doesn't seem to work in my situation. 看来这足以安排一个Task在UI线程上运行,但它似乎在我的情况下不起作用。 In my case, I have a UiThreadManager class which among other things has a RunAsynchronously method in it: 在我的例子中,我有一个UiThreadManager类,其中包含RunAsynchronously方法:

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

This part works just fine. 这部分工作正常。 The problem that I face is that when running unit tests, this class is replaced with a MockUiThreadManager class (both implement an IUiThreadManager interface which is used by the application code) and I can't seem to force this method to run on the UI thread: 我面临的问题是,在运行单元测试时,这个类被替换为MockUiThreadManager类(都实现了应用程序代码使用的IUiThreadManager接口),我似乎无法强制方法在UI线程上运行:

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

The MockUiThreadManager class has a static UiTaskScheduler property which is set (like below) on the UI thread, so I assumed that all code passing through the above method would run as expected on that thread: MockUiThreadManager类有一个static UiTaskScheduler属性,它在UI线程上设置(如下所示),所以我假设所有通过上述方法的代码都会在该线程上按预期运行:

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

However, when running a unit test, I noticed that it finished before the code that it was testing. 但是,在运行单元测试时,我注意到它在测试的代码之前完成。 I therefore added some breakpoints and called System.Threading.Thread.CurrentThread.ManagedThreadId in the Visual Studio Immediate Window at each breakpoint and sure enough, when the code passed through the above method, the thread ID changed. 因此,我在每个断点的Visual Studio立即窗口中添加了一些断点并调用了System.Threading.Thread.CurrentThread.ManagedThreadId ,当然,当代码通过上述方法传递时,线程ID也发生了变化。

So basically, I'm looking for a way to fake a Task based asynchronous call for the RunAsynchronously method that is run during unit tests that will ensure that the method actually runs synchronously on the UI thread. 基本上,我正在寻找一种方法来伪造基于Task的异步调用,该异步调用在单元测试期间运行的RunAsynchronously方法将确保该方法实际上在UI线程上同步运行。 Does anybody see what I have done wrong, or have any other suggestions? 有人看到我做错了什么,还是有其他建议?

UPDATE >>> 更新>>>

Ok, I've used the wrong terminology here regarding the UI thread. 好的,我在这里使用了关于UI线程的错误术语。 Unit tests don't run on the UI thread because there's no UI... however, the situation remains the same. 单元测试不在UI线程上运行,因为没有UI ...但是,情况保持不变。 To clarify, I just need my tests to run the application code synchronously and on the main single thread that it starts on. 为了澄清,我只需要我的测试来同步运行应用程序代码并在它启动的主要单个线程上运行。 The problem is that when the application is running, there is a lot of Task based asynchronous code and this needs to be tested via the MockUiThreadManager class synchronously. 问题是当应用程序运行时,有很多基于Task的异步代码,需要同步通过MockUiThreadManager类进行测试。

After continuing my search, I have now found the solution that I was looking for and it can be achieved in just a few lines of code, so definitely no need to implement my own SynchronizationContext class. 继续我的搜索之后,我现在找到了我正在寻找的解决方案,只需几行代码就可以实现,所以绝对不需要实现我自己的SynchronizationContext类。 Looking at how simple it is, I'm surprised that I didn't find it earlier. 看看它有多简单,我很惊讶我之前没有找到它。 In my MockUiThreadManager class that is used when running unit tests, I now have this code: 在运行单元测试时使用的MockUiThreadManager类中,我现在有了以下代码:

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

I can confirm that it does what it says and runs the method function synchronously on the same thread that the tests are run on. 我可以确认它完成它所说的并在运行测试的同一线程上同步运行method函数。

For completeness sake, the Task.RunSynchronously Method also has an override that takes a TaskScheduler , but that was unnecessary in my case, so I won't need my MockUiThreadManager.UiTaskScheduler property anymore. 为了完整起见, Task.RunSynchronously方法还有一个覆盖,它接受一个TaskScheduler ,但在我的情况下这是不必要的,所以我不再需要我的MockUiThreadManager.UiTaskScheduler属性了。

new SynchronizationContext() returns a new default SynchronizationObject which schedules work on the thread pool ( reference source ). new SynchronizationContext()返回一个新的默认SynchronizationObject,用于调度线程池( 引用源 )上的工作。

TaskScheduler.FromCurrentSynchronizationContext() returns a task scheduler that uses SynchronizationContext.Current which you just set to the thread pool sync context using SynchronizationContext.SetSynchronizationContext . TaskScheduler.FromCurrentSynchronizationContext()返回使用任务调度SynchronizationContext.Current您刚才设置使用线程池同步方面SynchronizationContext.SetSynchronizationContext

That means scheduling tasks on that scheduler will use the thread pool to execute them, and not a specific thread. 这意味着在该调度程序上的调度任务将使用线程池来执行它们,而不是特定的线程。

In general it is not even possible to schedule work on a specific thread, unless that thread has some sort of message queue. 通常,甚至不可能在特定线程上安排工作, 除非该线程具有某种类型的消息队列。 That's why you can schedule work to run on a UI thread. 这就是为什么你可以安排工作在UI线程上运行的原因。

I don't know which unit testing framework you use. 我不知道你使用哪个单元测试框架。 It might have a UI to show the test results, but that doesn't mean that the tests are run on that thread. 它可能有一个UI来显示测试结果,但这并不意味着测试是在该线程上运行的。


I'm not sure how to solve this, other than writing you own SynchronizationContext class. 除了编写自己的SynchronizationContext类之外,我不知道如何解决这个问题。 It also feels a bit weird that you want to unit test something that has to run on the UI thread, because in my opinion the UI itself is something that's not very suited to unit tests. 你想要对必须在UI线程上运行的东西进行单元测试也感觉有点奇怪,因为在我看来,UI本身是不适合单元测试的东西。 If you are using something like a ViewModel or a Controller then of course you can unit test those, but they should work regardless of the sync context you use. 如果您使用的是ViewModelController类的东西,那么当然您可以对它们进行单元测试,但无论您使用何种同步上下文,它们都应该可以正常运行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM