[英]How to deal with asynchronous methods and IDisposable in C#?
我使用xUnit进行了一些集成测试,需要拆除测试期间创建的一些资源。 为此,我在包含测试的类中实现了IDisposable
。
问题是我需要使用只有异步接口的客户端删除测试期间创建的资源。 但是, Dispose
方法是同步的。
我可以使用.Result
或.Wait()
等待异步调用的完成,但这可能会造成死锁( 这里的问题已经详细记录)。
鉴于我不能使用.Result
或.Wait()
,在Dispose
方法中调用异步方法的正确(和安全)方法是什么?
更新:添加(简化)示例以显示问题。
[Collection("IntegrationTests")]
public class SomeIntegrationTests : IDisposable {
private readonly IClient _client; // SDK client for external API
public SomeIntegrationTests() {
// initialize client
}
[Fact]
public async Task Test1() {
await _client
.ExecuteAsync(/* a request that creates resources */);
// some assertions
}
public void Dispose() {
_client
.ExecuteAsync(/* a request to delete previously created resources */)
.Wait(); // this may create a deadlock
}
}
我有类似的问题,特别是XUnit在这里是个问题孩子。 我通过将所有清理代码移动到测试中来“解决”,例如try..finally块。 它不太优雅,但工作更稳定,避免异步处理。 如果您有很多测试,可以添加一个减少样板的方法。
例如:
private async Task WithFinalizer(Action<Task> toExecute)
{
try
{
await toExecute();
}
finally
{
// cleanup here
}
}
// Usage
[Fact]
public async Task TestIt()
{
await WithFinalizer(async =>
{
// your test
});
}
这样做的另一个好处是,根据我的经验,清理通常高度依赖于测试 - 使用此技术为每个测试提供自定义终结器更容易(添加第二个可用作终结器的操作)
事实证明,xunit实际上包括一些支持来处理我面临的问题。 测试类可以实现IAsyncLifetime
以异步方式初始化和拆除测试。 界面如下:
public interface IAsyncLifetime
{
Task InitializeAsync();
Task DisposeAsync();
}
虽然这是我特定问题的解决方案,但它并没有解决从Dispose
调用异步方法的更普遍的问题(当前的答案都没有解决)。 我想我们需要等到.NET Core 3.0中的IAsyncDisposable
可用(感谢@MartinUllrich获取此信息)。
测试类执行多个彼此相关的测试。 测试类通常测试一个类或一组紧密协作的类。 有时测试类只测试一个函数。
通常,测试应该设计为不依赖于其他测试:测试A应该成功而不必运行测试B,反之亦然:测试可能不会假设其他测试。
通常,测试会创建一些前提条件,调用正在测试的函数并检查是否满足后置条件。 因此,每个测试通常都会创建自己的环境
如果一堆测试需要类似的环境,为了节省测试时间,为所有这些测试创建环境一次,运行测试并处置环境是很常见的。 这是您在测试课中所做的。
但是,如果在您的某个测试中创建了一个调用异步函数的任务,则应该在该测试中等待该任务的结果。 如果不这样做,则无法测试异步函数是否执行其要执行的操作,即:“创建一个在等待时返回...的任务”。
void TestA()
{
Task taskA = null;
try
{
// start a task without awaiting
taskA = DoSomethingAsync();
// perform your test
...
// wait until taskA completes
taskA.Wait();
// check the result of taskA
...
}
catch (Exception exc)
{
...
}
finally
{
// make sure that even if exception TaskA completes
taskA.Wait();
}
}
结论:创建任务的每个Test方法都应等待此类在完成之前完成
在极少数情况下,您不必等待测试完成之前完成任务。 也许看看如果你不等待任务会发生什么。 我仍然认为这是一个奇怪的想法,因为这可能影响其他测试,但是,嘿,这是你的测试类。
这意味着,您的Dispose必须确保等待测试方法结束时未完成的所有已启动任务。
List<Task> nonAwaitedTasks = new List<Task>();
var TestA()
{
// start a Task, for some reason you don't want to await for it:
Task taskA = DoSomethingAsync(...);
// perform your test
// finish without awaiting for taskA. Make sure it will be awaited before the
// class is disposed:
nonAwaitedTasks.Add(taskA);
}
public void Dispose()
{
Dispose(true);
}
protected void Dispose(bool disposing)
{
if (disposing)
{
// wait for all tasks to complete
Task.WaitAll(this.nonAwaitedTasks);
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.