如何通过丢失EndInvoke调用来修复资源泄漏?

时间:2015-03-04 14:38:28

标签: c# memory-leaks console.readline

我想使用this solution以超时方式呼叫Console.ReadLine()

delegate string ReadLineDelegate();

string ReadLine(int timeoutms)
{
    string resultstr = null;
    ReadLineDelegate d = Console.ReadLine;
    IAsyncResult result = d.BeginInvoke(null, null);
    result.AsyncWaitHandle.WaitOne(timeoutms);//timeout e.g. 15000 for 15 secs
    if (result.IsCompleted)
    {
        resultstr = d.EndInvoke(result);
        Console.WriteLine("Read: " + resultstr);        
    }
    else
    {
        Console.WriteLine("Timed out!");
        // Bug? resource leak? No d.EndInvoke(), which blocks until Console.ReadLine() returns
    }
    result.AsyncWaitHandle.Close();
    return resultstr;
}

但评论者警告说:

every ReadLine you call sits there waiting for input. 
If you call it 100 times, it creates 100 threads 
which don't all go away until you hit Enter 100 times!

...特别是因为我想在永久循环中反复调用它。

我了解每个BeginInvoke()都需要EndInvoke(),但我不希望在EndInvoke分支中进行阻止else调用。不知何故,我们需要abort正在运行的Console.ReadLine()调用而不是让它运行完成,因为它可能永远不会完成。

所以这些(复杂的)代码帮助我让Console.ReadLine在超时时返回,但不会结束Console.ReadLine以退出或以其他方式消失。

如何在不遇到资源泄漏的情况下使其正常工作?

注意:我按照MS Calling Sync calls asynchronously

的建议添加了AsyncWaitHandle.Close()

1 个答案:

答案 0 :(得分:0)

如上所述,在阅读了很多关于几个类似问题的评论后,我开始相信这里没有真正的解决方案。使用Begin/EndInvoke的Microsoft方式是

  • 相当复杂,并且:
  • 不够

更直接的方法是在另一个线程中运行同步调用,使用计时方法来跟踪超时,并使用Thread.Abort()来消除超时同步调用。

警告:

同步调用可能支持也可能不支持中止。例如,Console.ReadLine()将被中止,但是如果重新启动线程,则不再从控制台读取数据。

在我上面发布的原始问题上接受的解决方案使用第二个线程和计时方法。但是,它不会终止同步调用但会保持运行,因为后续异步调用需要它,这是一个很好的黑客。

使用第二个线程的代码实际上是直截了当的:

    public class MySyncProc
    {
        /// <summary>
        /// Value returned from the process after completion
        /// </summary>
        public string Result = null;

        ...other shared data...

        public MySyncProc() { }

        public void Run()
        {
            Result = LengthyProcess(...);
            return;
        }
    }

    public string Run(int TimeoutMs)
    {
        MySyncProc SyncP = new MySyncProc() { arg1 = ..., etc };
        //
        Thread T = new Thread(SyncP.Run);
        T.Start();
        //
        DateTime StopTime = DateTime.Now.AddMilliseconds(TimeoutMs);
        while (DateTime.Now < StopTime && SyncP.Result == null)
        {
            Thread.Sleep(200);
        }
        if (T.IsAlive)
        {
            T.Abort();
            Console.WriteLine("Aborted thread for: {0}", Name);
        }
        return SyncP.Result;
    }

如果您不喜欢轮询,请使用稍微复杂的AutoResetEvent,如上述已接受的解决方案。