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();
}
}
我终于修好了而且是在我发布的代码之外,你是对的。
我使用Json序列化/反序列化通过管道传递对象。传递的一个对象包含类型为"Event"的公共成员。
在我看了序列化字符串之后,我意识到它正在从服务器端向UI端传递一个Handle。
我很确定句柄在主循环中被一些模糊的机制关闭(当资源被释放时(?),这很奇怪,我认为GC负责这种事情....)
无论如何,我只是在需要序列化的属性上添加了一个[DataContract]
到具有[DataMember]
的对象,忽略了其他的。
没有更多的"ReleaseHandleFailed",没有更多的崩溃。如果能帮到谁的话。