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应用解释线程方面)

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

相关问题