【问题标题】:C# Invalid Handle thrown in UI thread on Control.Invoke在 Control.Invoke 的 UI 线程中抛出 C# Invalid Handle
【发布时间】:2015-09-17 09:23:55
【问题描述】:

我在这个问题上挠了好几天头...... 首先,让我们介绍一些上下文:


我正在编写一个应用程序,该应用程序通过 NamedPipeServer/Client 与 Windows 服务通信,并在 Windows 窗体中显示内容。

问题出在我的 System.Threading.Timer 回调中。此回调在其自身执行结束时动态重新调度,基于固定间隔减去执行时间。

在此回调中,我正在执行 Pipe 客户端请求,并根据答案使用 Invoke(经典跨线程消息)更新 UI。

代码:

private void OnTimerSamplesEvent(object data)
{
    Stopwatch watch = new Stopwatch();
    watch.Start();

    try
    {      
        PipeClient pipe = new PipeClient("MyPipe");
        PipeMessage.Message msg_struct  = new PipeMessage.Message();
        msg_struct.magic                = PipeMessage.Message.Magic;
        msg_struct.command              = PipeMessage.Message.Command.GetSamples;
        PipeMessage msg                 = new PipeMessage(msg_struct);

        byte[] reply_struct = pipe.Send(msg.ToByteArray());
        if (reply_struct != null)
        {
            PipeMessage reply = new PipeMessage(reply_struct);
            if (reply.m_Message.IsValid()
             && reply.m_Message.command == PipeMessage.Message.Command.GetSamples
             && reply.m_Message.getsamples.samples != null)
            {
                //Crashes here
                UpdateListView(this.listViewMySamples, reply.m_Message.getsamples.samples, new List<string>(), new List<string>() { "Misc" });
            }
        }
    }
    catch(Exception ex)
    {
        Logger.Log("MainForm::OnTimerSamplesEvent: " + ex.Message);
    }
    finally
    {
        // reschedule worker
        m_TimerSamples.Change(Math.Max(0, SamplesCheckIntervalms - watch.ElapsedMilliseconds), Timeout.Infinite);
    }
}

private void UpdateListView(ListView lv, List<MySample> samples, List<string> type_included, List<string> type_excluded)
{
    // Update view with modified samples / Remove out of sync
    int items_count = DelegatesUI.LVGetItemsCount(this, lv);    //<-- Crashes here

    // some other code, but commented out for testing
    ... 
}

private delegate int _LVGetItemsCount(ListView lv);
static private int _lvGetItemsCount(ListView lv) 
{ 
    return lv.Items.Count; 
}
static public int LVGetItemsCount(Form f, ListView lv)
{
    if (f.InvokeRequired)
    {
        object ret = f.Invoke(new _LVGetItemsCount(_lvGetItemsCount), lv);
        return (ret == null) ? 0 : (int)ret;
    }
    else
        return _lvGetItemsCount(lv);
}

这很好用,但由于某种原因,在执行大约 5 分钟后,主线程 在 System.IO.__Error.WinIOError 异常(无效句柄)上崩溃。当我查看工作线程状态时,它正在等待 Invoke (WaitHandle.WaitOne),可能正在等待 UI 线程发出其句柄信号。

UI 调用栈:

ERROR_INVALID_HANDLE   
à System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
à System.IO.__Error.WinIOError()
à System.Threading.EventWaitHandle.Set()
à System.Windows.Forms.Control.ThreadMethodEntry.Complete()
à System.Windows.Forms.Control.InvokeMarshaledCallbacks()
à System.Windows.Forms.Control.WndProc(Message& m)
à System.Windows.Forms.ScrollableControl.WndProc(Message& m)
à System.Windows.Forms.ContainerControl.WndProc(Message& m)
à System.Windows.Forms.Form.WndProc(Message& m)
à System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
à System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
à System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
à System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
à System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
à System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
à System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
à System.Windows.Forms.Application.Run(Form mainForm)
à agentui.Program.Main() dans c:\MBAM\cosmos\cosmos-agent\ui\Program.cs:ligne 17
à System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
à System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
à Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
à System.Threading.ThreadHelper.ThreadStart_Context(Object state)
à System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
à System.Threading.ThreadHelper.ThreadStart()

Worker 调用栈:

mscorlib.dll!System.Threading.WaitHandle.WaitOne(long timeout, bool exitContext)    Inconnu
mscorlib.dll!System.Threading.WaitHandle.WaitOne(int millisecondsTimeout, bool exitContext) Inconnu
System.Windows.Forms.dll!System.Windows.Forms.Control.WaitForWaitHandle(System.Threading.WaitHandle waitHandle) Inconnu
System.Windows.Forms.dll!System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control caller, System.Delegate method, object[] args, bool synchronous) Inconnu
System.Windows.Forms.dll!System.Windows.Forms.Control.Invoke(System.Delegate method, object[] args) Inconnu
>   agentui.exe!SharpUtils.DelegatesUI.InvokeAndClose(System.Windows.Forms.Control c, System.Delegate invok, object[] args) Ligne 38    C#
agentui.exe!SharpUtils.DelegatesUI.LVGetItemsCount(System.Windows.Forms.Form f, System.Windows.Forms.ListView lv) Ligne 541 C#
agentui.exe!agentui.MainForm.UpdateListView(System.Windows.Forms.ListView lv, System.Collections.Generic.List<agentsvc.MySample> samples, System.Collections.Generic.List<string> type_included, System.Collections.Generic.List<string> type_excluded) Ligne 142   C#
agentui.exe!agentui.MainForm.OnTimerSamplesEvent(object data) Ligne 283 C#
mscorlib.dll!System.Threading._TimerCallback.TimerCallback_Context(object state)    Inconnu
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) Inconnu
mscorlib.dll!System.Threading._TimerCallback.PerformTimerCallback(object state) Inconnu

问题是:什么会导致 WaitHandle 被关闭? 另外,由于这种情况发生在 UI 线程中我无法捕获任何内容,因为我这里没有用户代码(框架是“Application.Run(new MainForm());”)

欢迎任何想法。谢谢:)


编辑:我刚刚注意到 GC 线程在 Microsoft.Win32.SafeHandles.SafeWaitHandle 上抛出了一堆 ReleaseHandleFailed,因为该管道工作线程中的代码。不知道是不是一般的东西(好像不太正经)...

GC 线程:

>   mscorlib.dll!System.Runtime.InteropServices.SafeHandle.Dispose(bool disposing)  Inconnu
mscorlib.dll!System.Runtime.InteropServices.SafeHandle.Finalize()   Inconnu

工作线程:

[Transition Managé à Natif] 
>   System.Core.dll!System.IO.Pipes.PipeStream.ReadFileNative(Microsoft.Win32.SafeHandles.SafePipeHandle handle, byte[] buffer, int offset, int count, System.Threading.NativeOverlapped* overlapped, out int hr)   Inconnu
System.Core.dll!System.IO.Pipes.PipeStream.ReadCore(byte[] buffer, int offset, int count)   Inconnu
System.Core.dll!System.IO.Pipes.PipeStream.Read(byte[] buffer, int offset, int count)   Inconnu
agentui.exe!agentui.PipeClient.Send(byte[] message, int TimeOut) Ligne 172  C#
agentui.exe!agentui.MainForm.OnTimerSamplesEvent(object data) Ligne 275 C#
mscorlib.dll!System.Threading._TimerCallback.TimerCallback_Context(object state)    Inconnu
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) Inconnu
mscorlib.dll!System.Threading._TimerCallback.PerformTimerCallback(object state) Inconnu

编辑 2:我注意到如果我注释要从 Pipe 读取的代码(抛出 ReleaseHandleFailed 的代码),我就不再有这个问题了。

所以我也要发布那部分:

客户:

    public byte[] Send(byte[] message, int TimeOut = 1000)
    {
        byte[] answer = null;

        // Write query
        NamedPipeClientStream pipeStream = new NamedPipeClientStream(".", m_PipeName, PipeDirection.InOut);  
        try
        {
            // The connect function will indefinitely wait for the pipe to become available
            // If that is not acceptable specify a maximum waiting time (in ms)
            pipeStream.Connect(TimeOut);
            pipeStream.ReadMode = PipeTransmissionMode.Message;
            pipeStream.Write(message, 0, message.Length);
        }
        catch (Exception ex)
        {
            Logger.Log("PipeClient: " + ex.Message);
            try { pipeStream.Close(); } catch(Exception) {}
            return answer;
        }

        // Read reply
        try
        {
            using (MemoryStream ms = new MemoryStream())
            {
                using (BinaryWriter bw = new BinaryWriter(ms))
                {
                    // We read chunks while the message is incomplete
                    do
                    {
                        // Read the incoming chunk
                        byte[] buffer = new byte[256];
                        int bytesRead = pipeStream.Read(buffer, 0, buffer.Length);

                        // Append to the binary stream
                        bw.Write(buffer, 0, bytesRead);
                    }
                    while (!pipeStream.IsMessageComplete);

                    pipeStream.Close();                        
                    answer = ms.ToArray().Length > 0 ? ms.ToArray() : null;
                }
            }
        }
        catch (Exception ex)
        {
            Logger.Log("PipeClient: " + ex.Message);
            try { pipeStream.Close(); } catch (Exception) { }
        }

        return answer;
    }

服务器:

    private static void WorkerThread(object data)
    {
        PipeServer pipeServer = (PipeServer)data;
        while ( true )
        {
            try
            {
                PipeSecurity pipesecurity = new PipeSecurity();

                // Who has access to the pipe?
                pipesecurity.AddAccessRule(new PipeAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), PipeAccessRights.ReadWrite, AccessControlType.Allow));

                // Who can control the pipe? TODO: Find a SID representing the service
                pipesecurity.AddAccessRule(new PipeAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), PipeAccessRights.FullControl, AccessControlType.Allow)); 

                // Create the new async pipe 
                NamedPipeServerStream namedPipe = new NamedPipeServerStream(pipeServer.m_PipeName, 
                    PipeDirection.InOut, 
                    NamedPipeServerStream.MaxAllowedServerInstances, 
                    PipeTransmissionMode.Message,
                    PipeOptions.Asynchronous,
                    4096, 
                    4096, 
                    pipesecurity);

                // Wait for a connection with event
                var asyncResult = namedPipe.BeginWaitForConnection(_ => pipeServer.m_TerminateEvent.Set(), null);
                pipeServer.m_TerminateEvent.WaitOne();  // We block on that event until we have a client or a shutdown request
                pipeServer.m_TerminateEvent.Reset();    // We reset the event to reuse it

                if (asyncResult.IsCompleted)
                {
                    // We have a client, stop listening while we process it
                    namedPipe.EndWaitForConnection(asyncResult);

                    // Start a new thread for that client
                    Thread t = new Thread(() => ClientThread(namedPipe, pipeServer));
                    t.Start();
                }
                else
                {
                    // We requested a shutdown
                    namedPipe.Close();
                    namedPipe.Dispose();
                    break;
                }                    
            }
            catch (Exception ex)
            {
                Logger.Log("PipeServer::WorkerThread: " + ex.Message);
            }
        }

        // Notify the main thread we end the worker thread
        pipeServer.m_TerminatedEvent.Set();
    }

    private static void ClientThread(object pipe, object server)
    {
        NamedPipeServerStream namedPipe = (NamedPipeServerStream)pipe;
        PipeServer pipeServer           = (PipeServer)server;        

        try
        {     
            MemoryStream ms = new MemoryStream();
            BinaryWriter bw = new BinaryWriter(ms);

            // We read chunks while the message is incomplete
            do
            {
                // Read the incoming chunk
                byte[] buffer = new byte[256];
                int bytesRead = namedPipe.Read(buffer, 0, buffer.Length);

                // Append to the binary stream
                bw.Write(buffer, 0, bytesRead);
            }
            while (!namedPipe.IsMessageComplete);

            // Consume message.
            if (pipeServer.PipeEvent != null)
            {
                PipeEventArgs args = new PipeEventArgs( ms.ToArray() );
                pipeServer.PipeEvent(pipeServer, args);

                // Handle a possible reply
                if ( args.Reply != null && args.Reply.Length != 0 )
                    namedPipe.Write(args.Reply, 0, args.Reply.Length);
            } 
        }
        catch (Exception ex)
        {
            Logger.Log("PipeServer::ClientThread: " + ex.Message);
        }
        finally
        {
            namedPipe.Close();
            namedPipe.Dispose();
        }
    }

【问题讨论】:

  • 这样的代码失败的方式有很多,例如当窗口关闭时它总是表现不佳。但不是这样,那个句柄只能被终结器无效。还有其他我们看不到的代码正在关闭错误的句柄,破坏了 ManualResetEvent 的状态。我们无法帮助您找到它。
  • 感谢您的回答。 @Gserg 实际上那部分代码很好,因为在 UI 循环中推送了一条消息......我会尝试看看这是否会带来更多信息。
  • @Hans 表单关闭处理得当(为了清楚起见,我从代码中删除了该部分),对象上的 Monitor.TryEnter 充当锁......可能不是这里的问题。我不手动关闭句柄,所有东西都留给 GC,这就是为什么我不知道去哪里找。
  • 启用非托管调试并查看“调试”>“Windows”>“模块”窗口。寻找不应该存在的东西。反恶意软件永远位居榜首。
  • @hans 我的开发机器上没有反恶意软件。

标签: c# .net multithreading


【解决方案1】:

我终于修好了。它在我发布的代码之外,你是对的。

我正在使用 Json 序列化/反序列化 来通过管道传递对象。通过的对象之一是包含“事件”类型的公共成员。

查看序列化字符串后,我意识到它正在将一个句柄从服务器端传递到 UI 端。

我很确定句柄在 UI 端被主循环中的一些模糊机制关闭(当资源被释放时(?),这很奇怪,我认为 GC 负责这类事情...... )

无论如何,我只是在需要序列化的属性上添加了一个[DataContract] 到该对象,而[DataMember] 则忽略了其他属性。

没有更多的“ReleaseHandleFailed”,也没有更多的崩溃。 如果这可以帮助某人。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-05-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-22
    • 1970-01-01
    • 2013-09-18
    • 1970-01-01
    相关资源
    最近更新 更多