【问题标题】:How to dispose of an object that it created in a task?如何处置它在任务中创建的对象?
【发布时间】:2016-09-28 14:19:21
【问题描述】:

我有一个 foreach 循环,可以创建多个这样的任务:

[编辑:CreateDisposableAsync 返回一个任务[IDisposable]]

foreach(...)
{
   tasks.Add(CreateDisposableAsync());
}

然后我等待所有这些任务,并捕获任何异常:

try
{
   await Task.WhenAll(tasks);
}
catch (AggregateException)
{
   // handle exceptions
}

但是对 CreateDisposableAsync() 的调用会返回一个 IDisposable,无论任何任务中是否存在异常,我都希望将其处理掉。我该怎么做?

[编辑:事实证明,如果 CreateDisposableAsync() 函数抛出异常,它正在处理其创建的对象,因此原始代码没有任何问题。]

【问题讨论】:

  • tasks的类型是什么?你确定你需要处理吗? blogs.msdn.microsoft.com/pfxteam/2012/03/25/…
  • 最简单的方法是确保CreateDisposableAsync 清理自己的资源,以便负责处理它创建的任何需要处理的对象。
  • @LasseV.Karlsen 返回类型为Task
  • @Servy - 根据最新评论,我认为最简单的解决方案是让CreateDisposableAsync 调用在返回之前清理其自己的资源并返回Task 而不是Task<IDisposable>
  • 如果你不使用结果,为什么该方法返回任何东西,也就是说,除了Task

标签: c#


【解决方案1】:

来自cmets

问:除了处理结果之外,您是否(或想要)在调用代码中对结果进行其他处理

答:不,我没有

最简单的方法是让CreateDisposableAsync 方法在返回之前清理自己的资源并返回Task 而不是Task<IDisposable>。 OP 中显示的现有调用代码不必更改。

// change the signature
async Task CreateDisposableAsync(){
   // use using blocks for anything that needs to be disposed
   // try/finally is also acceptable
   using(var someDisposableInstance = new SomethingDisposable()){
      // implementation
   }
}

【讨论】:

    【解决方案2】:

    只有运行完成的任务才会返回可以处理的对象。只需将列表过滤到已完成的任务,然后选择结果。

    try
    {
        try
        {
           await Task.WhenAll(tasks);
        }
        catch (AggregateException)
        {
           // handle exceptions
        }
    
        //do other stuff with the returned task objects.
    }
    finally
    {    
        foreach(var item in tasks.Where(x=>x.Status == TaskStatus.RanToCompletion).Select(x=>x.Result))
        {
            //We use a try block so if Dispose throws it does not break the loop.
            try
            {
                item.Dispose();
            }
            catch(Exception ex)
            {
                //Log any exception on dispose.
            }
        }
    }
    

    或者如果你在WaitAll之后不打算做任何其他工作

    try
    {
       await Task.WhenAll(tasks);
    }
    catch (AggregateException)
    {
       // handle exceptions
    }
    finally
    {    
        foreach(var item in tasks.Where(x=>x.Status == TaskStatus.RanToCompletion).Select(x=>x.Result))
        {
            //We use a try block so if Dispose throws it does not break the loop.
            try
            {
                item.Dispose();
            }
            catch(Exception ex)
            {
                //Log any exception on dispose.
            }
        }
    }
    

    抛出错误的任务不会返回对象,无法在 CreateDisposableAsync() 之外处理它们,如果出现任何类型的错误,该函数将负责处理它们。

    public async Task<MyDisposeableClass> CreateDisposableAsync()
    {
        MyDisposeableClass myDisposeableClass = null;
        try
        {
            myDisposeableClass = new MyDisposeableClass();
    
            //...
    
            return myDisposeableClass;
        }
        catch
        {
            //dispose of the class if the instance was created.
            if(myDisposeableClass != null)
                myDisposeableClass.Dispose();
    
            //let the execption bubble up.
            throw;
        }
    }
    

    【讨论】:

    • 我不知道 OP 是否希望在 catch (AggregateException) 之后但在对象被处理之前完成额外的工作。如果在//do other stuff with the returned task objects. 中没有什么可做的,那么正如Servy 提到的,两个try 块可以合并,可以使用try/catch/finally
    • @Servy 否,因为 AggregateException 会将您踢出 try 块,因此如果您想对成功完成的结果执行某些操作,则需要在 catch (AggregateException) 块之后.编辑:但是,after reading the OP's comment 我看到他没有做任何额外的工作。所以可以合并。我会更新我的答案。
    • 非常正确,没有考虑到这一点(尽管基于最近的问题,我们都错过了这里实际发生的事情)。另外,我要注意,按照惯例,Dispose 不应该抛出异常,但当然,在实践中,它可能会。
    【解决方案3】:

    我在这里发布这个是因为它似乎是一种替代解决方案:

        private static async Task CallCreateDisposableAsync()
        {
             using (await CreateDisposableAsync()) { }
        }
    

    然后

    foreach(...)
    {
       tasks.Add(CallCreateDisposableAsync);
    }
    

    这样 using 语句可以处理创建的 IDisposable。

    【讨论】:

    • 如果我错了,请纠正我,但如果 CreateDisposableAsync 抛出异常,尽管它仍然不会被处理,因为它从未正式进入 using 块。这意味着您仍然会遇到同样的问题,如果您无法更改呼叫签名,Scott 的回答将是最佳选择。
    • @Igor 如果它抛出一个异常,那么永远不会有一个暴露的 IDisposable 对象可以被处理掉,所以没有什么可做的(从外部调用者的角度来看);在内部,任何返回一次性资源的方法都应该有一个 try/catch 并处置该资源,如果它可能在创建资源之后但返回之前的任何时间抛出异常)。
    • @Servy - 假设此方法创建 Disposable 实例成员,这不取决于CreateDisposableAsync 中抛出异常的位置吗?我基于这样一个事实做出了可能的假设,即 OP 提到 IDIsposable 结果即使在发生异常的情况下也应始终被处理掉。所以是的,假设CreateDisposableAsync 清理如果有异常没有问题。
    • @Igor 如果它创建了一次性实例成员,那么需要处理的不是方法的返回值,而是调用该方法的实例,这是完全不同的情况,由确保调用该方法的实例是一次性的并且被正确处理。
    猜你喜欢
    • 2018-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多