在没有等待的情况下使用语句内部异步,这安全吗?

async inside using statement without an await, is this safe?

如果在 using 语句中进行异步调用,并且调用的结果是异步处理的(即发生这种情况的方法是异步的,并且 returns 在加载和处理结果之前), using 语句可以超出范围吗? 换句话说,这样做安全吗:

async void LoadAndProcessStuff()
{
    using(var ctx = CreateDBContext()){
        someResource.LoadStuffsAsync().ForEachAsync(stuff => ctx.Stuffs.Add(stuff));
    }
}

async void LoadAndProcessStuff2()
{
    using(var ctx = CreateDBContext()){
        ctx.Stuffs.Select(stuff => someResource.LoadMoreStuffsAsync(stuff))
            .ForEachAsync(stuff => ctx.Stuffs2.AddRange(stuff.Result));
    }
}

或者 ctx 是否会在调用 ForEachAsync 时被处置并导致异常?

如果你想安全地做 async/parallel 东西(和背景东西),最好是使用 Tasksasync/awaitConfigureAwait。请记住,在执行离开方法之前 运行 using 中的内容总是更好,因此您必须相应地思考和封装您的代码。

以下是您可能想要执行的操作的一些示例:

  • 异步执行较长的 运行ning 任务
public async Task ParentMethodAsync() {
    DoSomeSyncStuff();
    await DoBigStuffAsync();
    DoSomeSyncStuffAfterAsyncBigStuff();
}
public async Task DoBigStuffAsync() {
    await Task.Run(() => {
        DoBigSyncStuff();
    });
}

使用该代码,您的执行将是:

  1. DoSomeSyncStuff
  2. 那么DoBigSyncStuff里面就会运行异步 DoBigStuffAsync
  3. ParentMethodAsync 将在 运行ning 之前等待此完成 DoSomeSyncStuffAfterAsyncBigStuff

  • 在后台异步执行一个长 运行ning 任务
public async Task ParentMethodAsync() {
    DoSomeSyncStuff();
    // original context/thread
    await DoBigStuffAsync();
    // same context/thread as original
    DoSomeSyncStuffAfterAsyncBigStuff();
}
public async Task DoBigStuffAsync() {
    // here you are on the original context/thread
    await Task.Run(() => {
        // this will run on a background thread if possible
        DoBigSyncStuff();
    }).ConfigureAwait(false);
    // here the context/thread will not be the same as original one
}

此处相同 运行ning 顺序和阻塞点,但使用 ConfigureAwait(false) 指定您不关心在原始上下文上同步。请注意 ParentMethodAsync context/thread 不受影响


  • 异步执行并同时继续执行
public async Task ParentMethodAsync() {
    DoSomeSyncStuff();
    Task bigStuffTask = DoBigStuffAsync();
    DoSomeSyncStuffBeforeEndOfBigStuff();
    await bigStuffTask;
    DoAnotherSyncStuffAfterAsyncBigStuff();
}
public async Task DoBigStuffAsync() {
    await Task.Run(() => {
        DoBigSyncStuff();
    });
}

使用该代码,您的执行将是:

  1. DoSomeSyncStuff
  2. 然后DoBigSyncStuff会在里面异步启动运行ning DoBigStuffAsync
  3. ParentMethodAsync 不会等待 bigStuffTask 完成,而是 运行 DoSomeSyncStuffBeforeEndOfBigStuff
  4. bigStuffTask(或DoBigStuffAsync)可能在之前或之后完成 DoSomeSyncStuffBeforeEndOfBigStuff 确实
  5. await bigStuffTask 将强制 ParentMethodAsync 等待 bigStuffTask 在 运行 之前完成 DoAnotherSyncStuffAfterAsyncBigStuff

  • 异步执行多个东西
public async Task ParentMethodAsync() {
    DoSomeSyncStuff();
    Task bigStuffTask = DoBigStuffAsync();
    Task bigStuff2Task = DoBigStuff2Async();
    await Task.WhenAll(bigStuffTask, bigStuff2Task);
    DoAnotherSyncStuffAfterAsyncBigStuff();
}
public async Task DoBigStuffAsync() {
    await Task.Run(() => {
        DoBigSyncStuff();
    });
}

使用该代码,您的执行将是:

  1. DoSomeSyncStuff
  2. 然后 DoBigSyncStuff 将在 DoBigStuffAsync
  3. 中异步启动 运行ning
  4. 然后 bigStuff2Task 将在 DoBigStuff2Async
  5. 中异步启动 运行ning
  6. ParentMethodAsync 将等待 bigStuffTaskbigStuff2Task 完成
  7. 一旦两者都完成,DoAnotherSyncStuffAfterAsyncBigStuff 将 运行(同步)

  • 执行任务而不是wait/care让它完成(即发即弃)
public async Task ParentMethodAsync() {
    DoSomeSyncStuff();
    Task bigStuffTask = DoBigStuffAsync();
    Task bigStuff2Task = DoBigStuff2Async();
    // this one is fired and forgotten
    DoFireAndForgetStuffAsync();
    await Task.WhenAll(bigStuffTask, bigStuff2Task);
    DoAnotherSyncStuffAfterAsyncBigStuff();
}
public async Task DoBigStuffAsync() {
    await Task.Run(() => {
        DoBigSyncStuff();
    });
}
public async void DoFireAndForgetStuffAsync() {
    Task.Run(() => {
        try {
            DoSomeFireAndForgetStuff();
        } catch (Exception e) {
            // handle your exception
        }
    });
}

使用该代码,您的执行将是:

  1. DoSomeSyncStuff
  2. 然后 DoBigSyncStuff 将在 DoBigStuffAsync
  3. 中异步启动 运行ning
  4. 然后 bigStuff2Task 将在 DoBigStuff2Async
  5. 中异步启动 运行ning
  6. 然后 DoSomeFireAndForgetStuff 将异步启动 运行ning 你的代码永远不会关心它是否完成 之后
  7. ParentMethodAsync 将等待 bigStuffTaskbigStuff2Task 完成
  8. 一旦两者都完成,DoAnotherSyncStuffAfterAsyncBigStuff 将 运行(同步)

请注意 async void 方法应该明智地使用并且应该始终在内部有自己的异常处理。 否则,如果抛出异常,因为您无法控制执行上下文的时间和内容,您最终可能会遇到意外和随机崩溃。


您还可以从 there for example, of with youtube livecode (e.g. this one from Xamarin Evolve 中学到其他东西,其中 James Clancey 通过一个简单的 xamarin 应用程序解释了线程方面)

我希望它能帮助你实现你想要的!

在异步方法中,"using" 关键字应该是安全的。编译器只是将其重写为 "finally" 表达式,其工作方式与异步方法中的所有其他异常处理一样。请注意,Dispose() 不应阻塞或过长 运行,它应该只是释放资源 - 除非您想削弱您的服务器。

这是安全用例的简单测试用例:

using System;
using System.Threading.Tasks;

namespace so {
    sealed class Garbage : IDisposable {
        public void Dispose() {
            Console.WriteLine("Disposing Garbage");
        }
    }

    // Garbage is safely Disposed() only after delay is done
    class Program {
        public static async Task Main() {
            using (Garbage g = new Garbage()) {
                Console.WriteLine("Delay Starting");
                await Task.Delay(1000);
                Console.WriteLine("Delay Finished");
            }
        }
    }
}

但是,如果您有一个返回任务而不是等待任务的非异步方法,则它可能不安全。那是因为同步方法只被调用一次。编译器没有生成协程或状态机,因此必须立即调用 Dispose() - 可能是在任务仍处于 运行.

public Task DoWorkAsync()
{
    using (var service = new Service())
    {
        var arg1 = ComputeArg();
        var arg2 = ComputeArg();

        // NOT SAFE - service will be disposed
        // There is a high-probability that this idiom results 
        // in an ObjectDisposedException
        return service.AwaitableMethodAsync(arg1, arg2);
    }
}

供参考:http://www.thebillwagner.com/Blog/Item/2017-05-03-ThecuriouscaseofasyncawaitandIDisposable