繁体   English   中英

如何在C#中处理异步方法和IDisposable?

[英]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.

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