C# 单元测试时等待async方法生成的所有线程
我正在WPF应用程序中对ViewModel进行单元测试,有一个委托命令调用一个方法,该方法在其中进一步调用async方法。在调用Assert语句之前,我必须等待每个任务完成。委托命令调用的方法如下 : 现在,我在单元测试中以以下方式等待该方法: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
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();
}
}