【问题标题】:Access to disposed closure in C#?访问 C# 中的已处置闭包?
【发布时间】:2013-07-12 17:14:30
【问题描述】:

我正在研究 Microsoft 企业库(数据应用程序块)——示例 sln。

他们有一个异步读取数据的示例(IAsync,尽管新版本(6)也支持async)。

但是 Resharper(or visual studio- nevermind) 向我显示:“访问已处置的闭包”:(首先我将显示图像,这样会更清晰,然后我将粘贴代码)

代码:

/*1*/    [Description("Execute a command that retrieves data asynchronously")]
/*2*/    static void ReadDataAsynchronously()
/*3*/    {
/*4*/        if (!SupportsAsync(asyncDB)) return;
/*5*/   
/*6*/        using(var doneWaitingEvent = new ManualResetEvent(false))
/*7*/        using(var readCompleteEvent = new ManualResetEvent(false))
/*8*/        {
/*9*/            try
/*10*/            {
/*11*/                // Create command to execute stored procedure and add parameters
/*12*/                DbCommand cmd = asyncDB.GetStoredProcCommand("ListOrdersSlowly");
/*13*/                asyncDB.AddInParameter(cmd, "state", DbType.String, "Colorado");
/*14*/                asyncDB.AddInParameter(cmd, "status", DbType.String, "DRAFT");
/*15*/                // Execute the query asynchronously specifying the command and the
/*16*/                // expression to execute when the data access process completes.
/*17*/                asyncDB.BeginExecuteReader(cmd,
/*18*/                    asyncResult = >
/*19*/                    {
/*20*/                        // Lambda expression executed when the data access completes.
/*21*/                        doneWaitingEvent.Set();
/*22*/                        try
/*23*/                        {
/*24*/                            using(IDataReader reader = asyncDB.EndExecuteReader(asyncResult))
/*25*/                            {
/*26*/                                Console.WriteLine();
/*27*/                                Console.WriteLine();
/*28*/                                DisplayRowValues(reader);
/*29*/                            }
/*30*/                        }
/*31*/                        catch (Exception ex)
/*32*/                        {
/*33*/                            Console.WriteLine("Error after data access completed: {0}", ex.Message);
/*34*/                        }
/*35*/                        finally
/*36*/                        {
/*37*/                            readCompleteEvent.Set();
/*38*/                        }
/*39*/                    }, null);
/*40*/   
/*41*/                // Display waiting messages to indicate executing asynchronouly
/*42*/                while (!doneWaitingEvent.WaitOne(1000))
/*43*/                {
/*44*/                    Console.Write("Waiting... ");
/*45*/                }
/*46*/   
/*47*/                // Allow async thread to write results before displaying "continue" prompt
/*48*/                readCompleteEvent.WaitOne();
/*49*/            }
/*50*/            catch (Exception ex)
/*51*/            {
/*52*/                Console.WriteLine("Error while starting data access: {0}", ex.Message);
/*53*/            }
/*54*/        }
/*55*/    }

问题:

为什么会发出这个警告?有一个manualreset-checked-signal(循环运行)阻止到达using 子句——这意味着——没有dispose 会调用。

那它为什么会大叫(警告)?

【问题讨论】:

  • 旁白:为什么在等待结果时使用异步调用?似乎在这里使用Tasks 会更直接。
  • 样本 SLN。此 SLN 包含另外 15 个示例,这些示例也使用了 TASK(async)。不过,这不是我的问题。
  • 我很好奇 - 是否存在这种情况的更简单变体,R# 足够聪明,可以将其识别为“安全”?如果是这样,我会感到惊讶。
  • 我的猜测只是 R# 并不完美。它无法知道(即它的静态分析不够先进,无法了解事件如何与异步调用交互)在 using 超出范围后从未调用过 lambda,因此它会警告您这可能会发生.
  • @RoyiNamir 样本很可能是正确的。它可能是。 R# 试图阻止 event.Set()using 块完成后被调用。通过异步调用或 any 在 lambda 中使用该对象,这在理论上是可能的。该警告并不意味着“这是一个错误”,而是“这可能是一个错误,您应该更仔细地查看这段代码”

标签: c# .net multithreading


【解决方案1】:

您将doneWaitingEvent 传递给可能超出 using 块范围的 lambda。 IE。在 lambda 执行时存在调用 Dispose 的风险。

【讨论】:

    【解决方案2】:

    它发出警告是因为引擎不够聪明,无法确定在委托代码完成之前永远不会退出 using 块。这就是为什么这是一个警告而不是错误。

    您可以放心地忽略此警告,您可以通过使用特殊 cmets 换行来让 resharper 抑制警告

    asyncDB.BeginExecuteReader(cmd, asyncResult =>
    {
        // Lambda expression executed when the data access completes.
        // ReSharper disable AccessToDisposedClosure
        doneWaitingEvent.Set();
        // ReSharper restore AccessToDisposedClosure
        try
        {
            using (IDataReader reader = asyncDB.EndExecuteReader(asyncResult))
            {
                Console.WriteLine();
                Console.WriteLine();
                DisplayRowValues(reader);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error after data access completed: {0}", ex.Message);
        }
        finally
        {
            // ReSharper disable AccessToDisposedClosure
            readCompleteEvent.Set();
            // ReSharper restore AccessToDisposedClosure
        }
    }, null);
    

    【讨论】:

    • 或者,如果您控制被调用的函数,则可以使用 JetBrains 的 [InstantHandle] 注释将其标记为对所有不同调用都是安全的。 See this question
    • @jtb 在我相信的最新版本之一中,这对 resharper 来说是新的。随意发布带有注释的答案。我会赞成它
    • 你也可以使用“ReSharper disable once AccessToDisposedClosure”来避免第二条评论
    【解决方案3】:

    您看到 ReSharper 警告的原因是 ReSharper 的代码流分析引擎不够强大,无法查看发生了什么:他们假设您的代码可以在没有设置 doneWaitingEvent 的情况下到达 using 子句的末尾,由于while 循环,这是不可能的:

    while (!doneWaitingEvent.WaitOne(1000)) {
        Console.Write("Waiting... ");
    }
    

    循环将继续打印"Waiting... " 行,直到调用doneWaitingEvent.Set();,从而防止您的代码到达using 块的末尾。其他警告也是如此。

    长话短说,可以放心地忽略此警告。添加 ReSharper 的“忽略此警告”cmets,并可选择向它们提交错误报告。

    【讨论】:

    • 对于未来的读者,在“可选地向他们提交错误报告”时,不要以为“哦,他们是一家大公司,不会听我的”而放弃这个想法。我有一个类似的“不必要的警告”,建议我应该使用 CreateLinkedCancelationTokenSource 的不同重载,我报告了它和 they fixed it right away
    • @ScottChamberlain 我会说这取决于...我在 很久 之前创建了两个非常严重的错误,但他们仍然没有修复它们。 Firstsecond
    • 看起来他们修复了第二个,第一个被分配(给修复第二个的同一个人)。他们不只是无视他们:)
    • @ScottChamberlain。我也填了一个错误,Resharper 团队在不到 2 周的时间内修复了它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-15
    • 2014-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多