c#在Control.Invoke的UI线程中抛出无效句柄

本文关键字:无效 句柄 线程 UI Control Invoke | 更新日期: 2023-09-27 18:13:03

我为这个问题挠头了好几天…首先,让我们放一些上下文:


我正在编写一个应用程序,它通过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 (waithhandle . 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线程中,我无法捕获任何东西,因为我在这里没有用户代码(帧是"应用程序。运行(新MainForm());")

欢迎任何意见。谢谢:)


编辑:我刚刚注意到GC线程在Microsoft.Win32.SafeHandles上抛出了一堆ReleaseHandleFailed。SafeWaitHandle,因为该管道工作线程中的代码。不知道是不是很平常(看起来不太合法)…

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:我已经注意到,如果我评论代码从管道读取(一个抛出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();
        }
    }

c#在Control.Invoke的UI线程中抛出无效句柄

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

我使用Json序列化/反序列化通过管道传递对象。传递的一个对象包含类型为"Event"的公共成员。

在我看了序列化字符串之后,我意识到它正在从服务器端向UI端传递一个Handle。

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

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

没有更多的"ReleaseHandleFailed",没有更多的崩溃。如果能帮到谁的话。