Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/336.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 单元测试时等待async方法生成的所有线程_C#_Wpf_Multithreading_Unit Testing_Asynchronous - Fatal编程技术网

C# 单元测试时等待async方法生成的所有线程

C# 单元测试时等待async方法生成的所有线程,c#,wpf,multithreading,unit-testing,asynchronous,C#,Wpf,Multithreading,Unit Testing,Asynchronous,我正在WPF应用程序中对ViewModel进行单元测试,有一个委托命令调用一个方法,该方法在其中进一步调用async方法。在调用Assert语句之前,我必须等待每个任务完成。委托命令调用的方法如下 : 现在,我在单元测试中以以下方式等待该方法: try { var task = Task.Factory.StartNew(() => { classobj.DelegateCommand.Execute(); }); v

我正在WPF应用程序中对ViewModel进行单元测试,有一个委托命令调用一个方法,该方法在其中进一步调用async方法。在调用Assert语句之前,我必须等待每个任务完成。委托命令调用的方法如下 :

现在,我在单元测试中以以下方式等待该方法:

try
{
    var task = Task.Factory.StartNew(() =>
        {
            classobj.DelegateCommand.Execute();
        });
    var afterTask = task.ContinueWith((myobject)=>
        {
            classobj.Load();
            Assert.AreEqual(true, someflag);
        }, TaskContinuationOptions.OnlyOnRanToCompletion);

}

但它仍然没有等待生成的所有内部任务完成。请建议

为什么不使用事件句柄(
AutoResetEvent
ManualResetEvent
),然后使用
WaitHande.WaitAll
阻止所有事件完成

这里的根本问题是您正在使用的方法是一个fire-and-forget异步方法。请特别参阅“和规则1”(看在上帝的份上,停止使用async void)。它实际上应该返回一个任务,然后等待该任务,在这种情况下,您的单元测试可以正确地等待它,并消除调用方执行另一个
任务的需要。运行
(优先于
任务。工厂。StartNew

然后可以异步完成单元测试(至少使用MSTest):

如果您坚持在这里使用fire-and-forget异步模式,那么您可能需要在测试中设置一个计时器循环,以等待另一个线程完成

有一个委托命令调用一个方法,该方法在其中进一步调用异步方法

@JimWooley正确地将此识别为问题的根源。避免
async void
的原因之一是
async void
方法(不容易)可测试

最好的解决方案是他建议的:

  • 将命令的实际逻辑分解为一个单独的返回方法
  • 在委托命令中,只需等待该方法
  • 然后,单元测试可以调用
    异步任务
    方法,而不是调用委托命令
但是,如果您真的坚持这样做,那么您需要安装一个自定义的
SynchronizationContext
,并跟踪异步操作的数量。或者您可以使用my来完成这一任务:

await AsyncContext.Run(() => classobj.DelegateCommand.Execute());
classobj.Load();
Assert.AreEqual(true, someflag);

除了在单元测试中避免
async void
(正如@JimWooley和@StephenCleary所指出的那样),您还可能希望在WPF线程上运行特定于WPF的代码

这是我通常做的,把这个答案放在一起是改进一些旧代码的好机会。类似的逻辑可以用于WinForms

// The method to test
public static async Task IdleAsync(CancellationToken token)
{
    while (true)
    {
        token.ThrowIfCancellationRequested();
        await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);
        Console.WriteLine("Idle!");
    }
}

// The unit test method
[TestMethod]
public async Task TestIdleAsync()
{
    var cts = new CancellationTokenSource(1000);
    var task = RunOnWpfThreadAsync(() => IdleAsync(cts.Token));
    try
    {
        await task;
    }
    catch
    {
        if (!task.IsCanceled)
            throw;
    }
}

// non-generic RunOnWpfThreadAsync
public static Task RunOnWpfThreadAsync(Func<Task> funcAsync)
{
    // convert Task to Task<object>: http://stackoverflow.com/q/22541734/1768303
    return RunOnWpfThreadAsync(async () => {
        await funcAsync().ConfigureAwait(false); return Type.Missing; });
}

// generic RunOnWpfThreadAsync<TResult>
public static async Task<TResult> RunOnWpfThreadAsync<TResult>(Func<Task<TResult>> funcAsync)
{
    var tcs = new TaskCompletionSource<Task<TResult>>();

    Action startup = async () =>
    {
        // this runs on the WPF thread
        var task = funcAsync();
        try
        {
            await task;
        }
        catch
        {
            // propagate exception with tcs.SetResult(task)
        }
        // propagate the task (so we have the result, exception or cancellation)
        tcs.SetResult(task);

        // request the WPF tread to end
        // the message loop inside Dispatcher.Run() will exit
        System.Windows.Threading.Dispatcher.ExitAllFrames();
    };

    // the WPF thread entry point
    ThreadStart threadStart = () =>
    {
        // post the startup callback
        // it will be invoked when the message loop starts pumping
        System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(
            startup, DispatcherPriority.Normal);
        // run the WPF Dispatcher message loop
        System.Windows.Threading.Dispatcher.Run();
    };

    // start and run the STA thread
    var thread = new Thread(threadStart);
    thread.SetApartmentState(ApartmentState.STA);
    thread.IsBackground = true;
    thread.Start();
    try
    {
        // propagate result, exception or cancellation
        return await tcs.Task.Unwrap().ConfigureAwait(false);
    }
    finally
    {
        // make sure the thread has fully come to an end
        thread.Join();
    }
}
//要测试的方法
公共静态异步任务IdleAsync(取消令牌令牌)
{
while(true)
{
token.ThrowIfCancellationRequested();
等待调度程序.Yield(DispatcherPriority.ApplicationIdle);
控制台。WriteLine(“空闲!”);
}
}
//单元测试法
[测试方法]
公共异步任务TestIdleAsync()
{
var cts=新的CancellationTokenSource(1000);
var task=RunOnWpfThreadAsync(()=>IdleAsync(cts.Token));
尝试
{
等待任务;
}
抓住
{
如果(!task.iscancelled)
投掷;
}
}
//非泛型RunOnWpfThreadAsync
公共静态任务RunOnWpfThreadAsync(Func-funcAsync)
{
//将任务转换为任务:http://stackoverflow.com/q/22541734/1768303
返回RunOnWpfThreadAsync(async()=>{
await funcAsync().ConfigureAwait(false);返回类型.Missing;});
}
//通用RunOnWpfThreadAsync
公共静态异步任务RunOnWpfThreadAsync(Func-funcAsync)
{
var tcs=new TaskCompletionSource();
操作启动=异步()=>
{
//这在WPF线程上运行
var task=funcAsync();
尝试
{
等待任务;
}
抓住
{
//使用tcs.SetResult传播异常(任务)
}
//传播任务(因此我们有结果、异常或取消)
设置结果(任务);
//请求WPF结束
//Dispatcher.Run()中的消息循环将退出
System.Windows.Threading.Dispatcher.exialFrames();
};
//WPF线程入口点
ThreadStart线程开始=()=>
{
//发布启动回调
//它将在消息循环开始泵送时被调用
System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(
启动、调度优先级。正常);
//运行WPF调度程序消息循环
System.Windows.Threading.Dispatcher.Run();
};
//启动并运行STA线程
var thread=新线程(threadStart);
SetApartmentState(ApartmentState.STA);
thread.IsBackground=true;
thread.Start();
尝试
{
//传播结果、异常或取消
返回wait tcs.Task.Unwrap().configurewait(false);
}
最后
{
//确保线已完全结束
thread.Join();
}
}

你在用
后任务做什么
,你是
后任务。等等()
等待后续任务;
在其上?理想情况下,您应该使用专门为测试异步方法而设计的测试框架。它将设置一个消息泵,处理返回
任务的测试,并根据任务的结果验证测试,而不仅仅是测试方法的结果你可以自己把所有的框架都放好,最好让测试框架来做。为什么我要在任务完成后再做。等一下,当我应用了ranTocompletion语句时,这意味着continuewith中的语句将在上述方法完成时执行,如果我错了,请纠正我。@Servy:我是单元测试viewmodels和使用mbunit,因为我不知道使用单独的框架进行测试。请提供一些links@priya我无法告诉您该框架是否支持异步方法,或者其他类似的框架是否支持异步方法
[TestMethod]
public async Task TestItAsync()
{
   await Methodcalled();
   Assert.IsTrue(Something);
}
await AsyncContext.Run(() => classobj.DelegateCommand.Execute());
classobj.Load();
Assert.AreEqual(true, someflag);
// The method to test
public static async Task IdleAsync(CancellationToken token)
{
    while (true)
    {
        token.ThrowIfCancellationRequested();
        await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);
        Console.WriteLine("Idle!");
    }
}

// The unit test method
[TestMethod]
public async Task TestIdleAsync()
{
    var cts = new CancellationTokenSource(1000);
    var task = RunOnWpfThreadAsync(() => IdleAsync(cts.Token));
    try
    {
        await task;
    }
    catch
    {
        if (!task.IsCanceled)
            throw;
    }
}

// non-generic RunOnWpfThreadAsync
public static Task RunOnWpfThreadAsync(Func<Task> funcAsync)
{
    // convert Task to Task<object>: http://stackoverflow.com/q/22541734/1768303
    return RunOnWpfThreadAsync(async () => {
        await funcAsync().ConfigureAwait(false); return Type.Missing; });
}

// generic RunOnWpfThreadAsync<TResult>
public static async Task<TResult> RunOnWpfThreadAsync<TResult>(Func<Task<TResult>> funcAsync)
{
    var tcs = new TaskCompletionSource<Task<TResult>>();

    Action startup = async () =>
    {
        // this runs on the WPF thread
        var task = funcAsync();
        try
        {
            await task;
        }
        catch
        {
            // propagate exception with tcs.SetResult(task)
        }
        // propagate the task (so we have the result, exception or cancellation)
        tcs.SetResult(task);

        // request the WPF tread to end
        // the message loop inside Dispatcher.Run() will exit
        System.Windows.Threading.Dispatcher.ExitAllFrames();
    };

    // the WPF thread entry point
    ThreadStart threadStart = () =>
    {
        // post the startup callback
        // it will be invoked when the message loop starts pumping
        System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(
            startup, DispatcherPriority.Normal);
        // run the WPF Dispatcher message loop
        System.Windows.Threading.Dispatcher.Run();
    };

    // start and run the STA thread
    var thread = new Thread(threadStart);
    thread.SetApartmentState(ApartmentState.STA);
    thread.IsBackground = true;
    thread.Start();
    try
    {
        // propagate result, exception or cancellation
        return await tcs.Task.Unwrap().ConfigureAwait(false);
    }
    finally
    {
        // make sure the thread has fully come to an end
        thread.Join();
    }
}