CurrentThreadTaskScheduler未完成同步 [英] CurrentThreadTaskScheduler does not finish Synchronous

查看:149
本文介绍了CurrentThreadTaskScheduler未完成同步的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试着写一个单元测试的视图模型,但我被困试图验证的ICommand调用异步方法时的两倍。

I try to write a Unit-Test for a View Model, but i got stuck when trying to verify a ICommand calls a asynchronous method twice.

我使用起订量为我的依赖关系。
我设置了这样的异步方法。

i use Moq for my dependencies. I set up the async Method like this.

this.communicationServiceFake
     .Setup(x => x.WriteParameterAsync(It.IsAny<string>(), It.IsAny<object>()))
     .ReturnsAsyncIncomplete();

该扩展ReturnsAsyncIncomplete不会立即在关键字的await返回,基本上类似的东西在这里找到:的异步/等待和code覆盖

我用自己的TaskSheduler以确保方法是Task.Factory.StartNew返回之前的竞争。

I use a own TaskSheduler to ensure that the Methods are compete before the Task.Factory.StartNew returns.

Task.Factory.StartNew(() => viewModel.Command.Execute(null), 
    CancellationToken.None, TaskCreationOptions.None, new CurrentThreadTaskScheduler ());

基本上CurrentThreadTaskScheduler来自这里:等待所有任务完成的单元测试并不会是这样的:

public class CurrentThreadTaskScheduler : TaskScheduler
{
    protected override void QueueTask(Task task)
    {
        this.TryExecuteTask(task);
    }

    protected override bool TryExecuteTaskInline(Task task, bool wasPreviouslyQueued)
    {
        return this.TryExecuteTask(task);
    }

    protected override IEnumerable<Task> GetScheduledTasks()
    {
        yield break;
    }
}

命令不会调用以下code:

the Command does call the following code:

await this.communicationService.WriteParameterAsync("Parameter1", true);
await this.communicationService.WriteParameterAsync("Parameter2", true);

然后验证:

  this.communicationServiceFake
       .Verify(t => t.WriteParameterAsync("Parameter1", true), Times.Once);
  this.communicationServiceFake
       .Verify(t => t.WriteParameterAsync("Parameter2", true), Times.Once);

有时候它说第二个呼叫并没有发生。
如果我有ThreadSleep取代我Task.Factory.StartNew,以确保一切完成后,一切工作正常甚至认为它记似乎是正确不必要地耽误我的单元测试。

sometimes it says the 2nd call did not happen. If i replace my Task.Factory.StartNew with a ThreadSleep to ensure everything finished, all works fine even thought it does note seem right to delay my unit tests unnecessarily.

为什么我的CurrentThreadTaskScheduler让Task.Factory.StartNew回到我的Command.Execute之前完成?

Why does my CurrentThreadTaskScheduler allow the Task.Factory.StartNew to return before my Command.Execute finished?

推荐答案

GetIncompleteTask 扩展方法://bernhard-richter.blogspot。 co.at/2014/09/asyncawait-and-$c$c-coverage.html\">the~~V链接的博客文章使用 Task.Run ,从而引入另一个线程(和竞争状态)。

The GetIncompleteTask extension method in the linked blog post uses Task.Run, hence introducing another thread (and a race condition).

CurrentThreadTaskScheduler 将只对工作异步无效如果所有的任务都已经完成了,这到底是什么方法,你'重的避免的有 ReturnsAsyncIncomplete

The CurrentThreadTaskScheduler will only work for async void methods if all tasks are already completed, which is exactly what you're avoiding with ReturnsAsyncIncomplete.

下面是发生了什么:


  1. 执行 CurrentThreadTaskScheduler 运行。它等待与正在上运行的线程池一个不完整的任务。在这一点上,<​​code> StartNew 的任务就完成了。

  2. 线程池线程完成未完成的任务,并在电流的线程(线程池线程)执行它的延续。

  1. Execute runs in CurrentThreadTaskScheduler. It awaits an incomplete task that is running on the threadpool. At this point, the StartNew task is complete.
  2. The thread pool thread finishes the incomplete task, and executes its continuations on the current thread (the thread pool thread).

什么你正在试图做的是单元测试的异步无效法全覆盖(即异步)。这肯定是不容易的。

What you're trying to do is unit test an async void method with full coverage (i.e., asynchronously). This is certainly not easy.

推荐的解决方案

使用某种形式的异步 -Aware 的ICommand 如我的my MSDN文章。然后,你可以使用等待viewModel.Command.ExecuteAsync(空)在你的单元测试,而不是混乱与周围的自定义任务调度的。

Use some form of an async-aware ICommand such as the ones I describe in my MSDN article. Then you can just use await viewModel.Command.ExecuteAsync(null) in your unit test and not mess around with custom task schedulers at all.

此外,异步嘲讽真的可以简化;没有必要自定义awaiter因为.NET框架已经有一个: Task.Yield 。所以,你可以只用一个单一的扩展方法替换所有code(未经测试):

Also, the asynchronous mocking can really be simplified; there's no need for a custom awaiter because the .NET framework already has one: Task.Yield. So you can replace all that code with just a single extension method (untested):

public static IReturnsResult<TMock> ReturnsIncompleteAsync<TMock, TResult>(this IReturns<TMock, Task<TResult>> mock, TResult value) where TMock : class
{
  return mock.Returns(async () =>
  {
    await Task.Yield();
    return value;
  });
}

保持异步无效

如果你真的想保持你现有的的ICommand 的实现,并希望单元测试异步无效方法的希望全code覆盖,那么你真的需要一个自定义的的SynchronizationContext 而不是一个自定义的的TaskScheduler 。要做到这一点,最简单的方法是安装我的 AsyncEx的NuGet包,并使用 AsyncContext

If you really want to keep your existing ICommand implementations, and want to unit test async void methods, and want full code coverage, then you really need a custom SynchronizationContext instead of a custom TaskScheduler. The easiest way to do this is to install my AsyncEx NuGet package and use AsyncContext:

// Blocks the current thread until all continuations have completed.
AsyncContext.Run(() => viewModel.Command.Execute(null));

另外,我推荐你使用上面,而不是描述的 Task.Yield 办法的定制awaitable-ON-一个线程池。

Also, I recommend you use the Task.Yield approach described above instead of the custom-awaitable-on-a-thread-pool.

这篇关于CurrentThreadTaskScheduler未完成同步的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆