匿名异步,什么是正确的方式

本文关键字:方式 什么 异步 | 更新日期: 2023-09-27 18:11:14

我有一个简单的类做同步的事情,

public static class Synchronous
{
    public static void DoTheWholeThing()
    {
        AStuff aStuff;
        using (var a = new A())
        {
            aStuff = a.GetStuff();
        }
        BStuff bStuff;
        using (var b = new B())
        {
            bStuff = b.GetStuff();
        }
        var combination = CombineStuff(aStuff, bStuff);
    }
    private static Combination CombineStuff(AStuff aStuff, BStuff bStuff)
    {
        //// Magic Here
    }
}

显然,这段代码没有完全定义,但它确实说明了我的问题。

现在,AB类都负责从不同的远程数据源检索数据。因此,AB的开发人员实现了称为GetStuffAsync的异步入口点,分别返回Task<AStuff>Task<BStuff>

我想最大限度地利用异步方法并并发调用它们,这样我就可以减少代码的总体等待时间。

到目前为止,这是我所编造的。

public static class Asynchronous
{
    public async static Task DoTheWholeThing(CancellationToken cancellationToken)
    {
        var getAStuffTask  = new Func<Task<AStuff>>(
                async () =>
                    {
                        using (var a = new A())
                        {
                            return await a.GetStuffAsync(cancellationToken);
                        }
                    })();
        var getBStuffTask  = new Func<Task<BStuff>>(
                async () =>
                    {
                        using (var b = new B())
                        {
                            return await b.GetStuffAsync(cancellationToken);
                        }
                    })();
        var combination = CombineStuff(
            await getAStuffTask,
            await getBStuffTask);
    }
    private Combination CombineStuff(AStuff aStuff, BStuff bStuff)
    {
        //// Magic Here
    }
}
除了这段代码看起来很像javascript模块模式之外,这是正确的方法吗?我不认为我应该使用Task.Run,因为这段代码显然不是CPU限制。

这似乎有点"笨拙",我需要实例化类型化委托来做到这一点。有没有更好的办法?

编辑

我在命名函数和延续之间进退两难

匿名异步,什么是正确的方式

只要将匿名方法提取为命名方法,代码就会变得非常简单:

public async static Task DoTheWholeThing(CancellationToken cancellationToken)
{
    var getAStuffTask = GetAStuffAsync(cancellationToken);
    var getBStuffTask = GetBStuffAsync(cancellationToken);
    var combination = CombineStuff(
        await getAStuffTask,
        await getBStuffTask);
}
private static async Task<AStuff> GetAStuffAsync(CancellationToken cancellationToken)
{
    using (var a = new A())
    {
        return await a.GetStuffAsync(cancellationToken);
    }
}
private static async Task<BStuff> GetBStuffAsync(CancellationToken cancellationToken)
{
    using (var b = new B())
    {
        return await b.GetStuffAsync(cancellationToken);
    }
}

也就是说,如果你真的想要坚持匿名方法,你可以创建一个助手方法,它将允许泛型类型推断和lambda隐式地计算出委托的类型:

public async static Task DoTheWholeThing(CancellationToken cancellationToken)
{
    var getAStuffTask = Start(async () =>
            {
                using (var a = new A())
                {
                    return await a.GetStuffAsync(cancellationToken);
                }
            });
    var getBStuffTask = Start(async () =>
            {
                using (var b = new B())
                {
                    return await b.GetStuffAsync(cancellationToken);
                }
            });
    var combination = CombineStuff(
        await getAStuffTask,
        await getBStuffTask);
}
public static Task<T> Start<T>(Func<Task<T>> asyncOperation)
{
    return asyncOperation();
}

使用TPL延续在任务完成后立即调用Dispose

public async static Task DoTheWholeThing(CancellationToken cancellationToken)
{
    var a = new A();
    var b = new B();
    // start the tasks and store them for awaiting later
    var getAStuffTask = a.GetStuffAsync(cancellationToken);
    var getBStuffTask = b.GetStuffAsync(cancellationToken);
    // queue up continuations to dispose of the resource as soon as it is not needed
    getAStuffTask.ContinueWith(() => a.Dispose());
    getBStuffTask.ContinueWith(() => b.Dispose());
    // await as normal
    var combination = CombineStuff(
        await getAStuffTask,
        await getBStuffTask);
}

我不确定是否将整个方法包装在一个额外的using块中会完成任何事情,但它可能会提供安心。

您不需要将异步调用包装在委托中以使它们立即执行。如果您直接调用GetStuffAsync方法而不等待它们,您将得到相同的结果。

public static class Asynchronous
{
    public async static Task DoTheWholeThing(CancellationToken cancellationToken)
    {
        using (var a = new A())
        using (var b = new B()) {
            var taskA = a.GetStuffAsync(cancellationToken);
            var taskB = b.GetStuffAsync(cancellationToken);
            await Task.WhenAll(new [] { taskA, taskB });
            var combination = CombineStuff(taskA.Result, taskB.Result);
        }
    }
    private Combination CombineStuff(AStuff aStuff, BStuff bStuff)
    {
        //// Magic Here
    }
}

请注意,这确实使ab对象在调用CombineStuff期间保持活动状态,如@Servy所述。如果这是一个问题,可以将Task对象的声明移出using块,如下所示:

public static class Asynchronous
{
    public async static Task DoTheWholeThing(CancellationToken cancellationToken)
    {
        Task taskA;
        Task taskB;
        using (var a = new A())
        using (var b = new B()) {
           taskA = a.GetStuffAsync(cancellationToken);
           taskB = b.GetStuffAsync(cancellationToken);
           await Task.WhenAll(new [] { taskA, taskB });
         }
         var combination = CombineStuff(taskA.Result, taskB.Result);
    }
    private Combination CombineStuff(AStuff aStuff, BStuff bStuff)
    {
        //// Magic Here
    }
}

尽管只要ab任务都在运行,而不是在它们返回时处理它们。