async里面没有等待使用语句,这样安全吗?

时间:2018-01-06 11:40:03

标签: c# asynchronous using

如果在using语句中进行异步调用,并且异步处理调用的结果(即发生这种情况的方法是异步并在加载和处理结果之前返回),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));
    }
}

或者可以在调用ForEachAsync时处理ctx并导致异常?

2 个答案:

答案 0 :(得分:1)

在异步方法中,“ using”关键字应该是安全的。编译器只是将其重写为“最终”表达式,该表达式的工作方式类似于异步方法中的所有其他异常处理。请注意,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");
            }
        }
    }
}

但是,如果您有一个非异步方法返回一个Task而不是等待它,则它可能是不安全的。这是因为同步方法仅被称为一次。编译器没有生成协程或状态机,因此有可能在Task仍在运行时立即调用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

答案 1 :(得分:-3)

如果您想安全地执行异步/并行内容(以及后台内容),最好使用Tasksasync/awaitConfigureAwait。请记住,在执行离开方法之前运行using内的东西总是更好,因此您必须相应地思考和封装代码。

以下是您可能想要做的一些示例:

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

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

  1. DoSomeSyncStuff
  2. 然后DoBigSyncStuff将在内部异步运行 DoBigStuffAsync
  3. ParentMethodAsync将在运行前等待此操作完成 DoSomeSyncStuffAfterAsyncBigStuff
    • 在背景上异步执行长时间运行的任务
    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
    }
    

    这里有相同的运行顺序和阻止点,但是ConfigureAwait(false)指定您不关心原始上下文的同步。请注意ParentMethodAsync上下文/线程不受影响

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

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

    1. DoSomeSyncStuff
    2. 然后DoBigSyncStuff将开始在内部异步运行 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. 内异步运行
      4. 然后bigStuff2Task将开始在DoBigStuff2Async
      5. 内异步运行
      6. ParentMethodAsync将等待bigStuffTaskbigStuff2Task完成
      7. 一旦完成,DoAnotherSyncStuffAfterAsyncBigStuff将(同步)
      8. 运行
        • 执行内容并且不要等待/关心它完成(即发即弃)
        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. 内异步运行
        4. 然后bigStuff2Task将开始在DoBigStuff2Async
        5. 内异步运行
        6. 然后DoSomeFireAndForgetStuff将开始异步运行 并且您的代码永远不会知道它是否完成 之后
        7. ParentMethodAsync将等待bigStuffTaskbigStuff2Task完成
        8. 一旦完成,DoAnotherSyncStuffAfterAsyncBigStuff将(同步)
        9. 运行

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

          您可以从there获取其他内容,例如youtube livecode(例如this one from Xamarin Evolve,其中James Clancey通过简单的xamarin应用解释线程方面)

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