避免在跨线程WinForm事件处理中遇到Invoke / BeginInvoke的麻烦吗? - c#

我仍然为WinForm UI中的后台线程所困扰。为什么?以下是一些问题:

显然,最重要的问题是,除非在创建控件的同一线程上执行,否则无法修改控件。
如您所知,在创建控件之前,Invoke,BeginInvoke等不可用。
即使RequiresInvoke返回true之后,BeginInvoke仍然可以抛出ObjectDisposed,即使它没有抛出,如果控件被破坏,它也可能永远不会执行代码。
即使在RequiresInvoke返回true之后,Invoke仍可以无限期挂起,等待与对Invoke的调用同时放置的控件执行。

我正在寻找一个解决该问题的优雅方法,但是在我详细介绍所寻找的内容之前,我想我会澄清问题。这是为了解决通用问题,并在其后放置一个更具体的示例。对于此示例,假设我们正在通过Internet传输大量数据。用户界面必须能够显示已在进行中的传输的进度对话框。进度对话框应不断且快速地更新(每秒更新5至20次)。用户可以随时关闭进度对话框,并在需要时再次调用它。此外,让我们假装参数是因为如果对话框可见,则必须处理每个进度事件。用户可以在进度对话框上单击“取消”,并通过修改事件args取消操作。

现在,我需要一个适合以下约束条件的解决方案:

允许工作线程在控件/窗体上调用方法,然后阻止/等待执行完成。
允许对话框本身在初始化或类似操作时调用相同的方法(因此不要使用invoke)。
解决方案或调用事件没有实现负担,解决方案仅应更改事件订阅本身。
适当地处理对可能正在处理的对话框的阻塞调用。不幸的是,这并不像检查IsDisposed那样容易。
必须能够与任何事件类型一起使用(假定类型为EventHandler的委托)
不得将异常转换为TargetInvocationException。
该解决方案必须与.Net 2.0及更高版本一起使用

那么,鉴于上述限制,可以解决此问题吗?我搜索和挖掘了无数博客和讨论,可惜我还是一无所获。

更新:我确实意识到这个问题没有简单的答案。我只在该网站上待了几天,并且看到一些有很多回答问题经验的人。我希望这些人中的一个已经足够解决了这个问题,以至于我不用花一周左右的时间来建立一个合理的解决方案。

更新#2:好的,我将尝试更详细地描述问题,并观察一下(如果有)什么。允许我们确定其状态的以下属性引起了一些关注...

Control.InvokeRequired =如果在当前线程上运行或IsHandleCreated对所有父级返回false,则记录为返回false。
我为InvokeRequired实现感到困扰,它有可能抛出ObjectDisposedException甚至可能重新创建对象的句柄。而且由于InvokeRequired可以在我们无法调用(正在处置)时返回true,并且即使我们可能需要使用invoke(正在创建)也可以返回false,所以在所有情况下都无法信任它。我唯一可以看到可以信任InvokeRequired返回false的情况是IsHandleCreated在调用之前和之后都返回true(顺便说一句,InvokeRequired的MSDN文档确实提到了对IsHandleCreated的检查)。
Control.IsHandleCreated =如果已将句柄分配给控件,则返回true;否则,返回true。否则为假。
尽管IsHandleCreated是一个安全的调用,但是如果控件正在重新创建其句柄,则它可能会崩溃。通过访问IsHandleCreated和InvokeRequired时执行锁定(控件),似乎可以解决此潜在问题。
Control.Disposed =如果控件正在处置,则返回true。
Control.IsDisposed =如果已处置控件,则返回true。
我正在考虑订阅Disposed事件,并检查IsDisposed属性以确定BeginInvoke是否将完成。这里最大的问题是在Dispose-> Disposed过渡期间缺少同步锁。如果您预订了Disposed事件,并在此之后验证Dispose == false和&IsDisposed == false,则可能仍然永远不会看到Disposed事件触发。这是由于以下事实:Dispose的实现将Dispose = false,然后将Disposed = true。这为您提供了一个机会(但是很小)来将Dispose和IsDisposed都读为已处置控件上的false。

...我的头很痛:(希望以上信息能为遇到这些麻烦的人提供更多的启发。非常感谢您为此付出的宝贵时间。

解决问题...下面是Control.DestroyHandle()方法的后半部分:

if (!this.RecreatingHandle && (this.threadCallbackList != null))
{
    lock (this.threadCallbackList)
    {
        Exception exception = new ObjectDisposedException(base.GetType().Name);
        while (this.threadCallbackList.Count > 0)
        {
            ThreadMethodEntry entry = (ThreadMethodEntry) this.threadCallbackList.Dequeue();
            entry.exception = exception;
            entry.Complete();
        }
    }
}
if ((0x40 & ((int) ((long) UnsafeNativeMethods.GetWindowLong(new HandleRef(this.window, this.InternalHandle), -20)))) != 0)
{
    UnsafeNativeMethods.DefMDIChildProc(this.InternalHandle, 0x10, IntPtr.Zero, IntPtr.Zero);
}
else
{
    this.window.DestroyHandle();
}

您会注意到ObjectDisposedException被分派给所有等待的跨线程调用。紧随其后的是对this.window.DestroyHandle()的调用,该调用继而破坏了窗口并设置了对IntPtr.Zero的句柄引用,从而防止了对BeginInvoke方法(或更确切地说,处理了BeginInvoke和Invoke的MarshaledInvoke)的进一步调用。这里的问题是,在threadCallbackList上的锁释放之后,可以在控件的线程将窗口句柄清零之前插入新的条目。我看到的似乎是这种情况,尽管这种情况很少见,通常足以停止发布。

更新#4:

很抱歉继续拖下去;但是,我认为这里值得记录。我已经设法解决了上面的大多数问题,并且在寻找一种可行的解决方案。我遇到了另一个我担心的问题,但是直到现在,还没有看到“荒野”。

这个问题与编写Control.Handle属性的天才有关:

    public IntPtr get_Handle()
    {
        if ((checkForIllegalCrossThreadCalls && !inCrossThreadSafeCall) && this.InvokeRequired)
        {
            throw new InvalidOperationException(SR.GetString("IllegalCrossThreadCall", new object[] { this.Name }));
        }
        if (!this.IsHandleCreated)
        {
            this.CreateHandle();
        }
        return this.HandleInternal;
    }

这本身还不错(不管我对get {}修改的看法如何);但是,与InvokeRequired属性或Invoke / BeginInvoke方法结合使用时,效果很差。这是调用的基本流程:

if( !this.IsHandleCreated )
    throw;
... do more stuff
PostMessage( this.Handle, ... );

这里的问题是,我可以从另一个线程成功通过第一个if语句,然后句柄被控件的线程破坏,从而导致获取Handle属性在我的线程上重新创建窗口句柄。然后,这可能导致在原始控件的线程上引发异常。我真的很沮丧,因为没有办法防止这种情况发生。如果他们仅使用InternalHandle属性并测试了IntPtr.Zero的结果,那么这将不是问题。

参考方案

如上所述,您的方案非常适合BackgroundWorker-为什么不只使用它呢?您对解决方案的要求过于笼统,而且不合理-我怀疑是否有任何解决方案可以满足所有要求。

如何在ASP.NET Core Web应用程序中增加JSON反序列化MaxDepth限制 - c#

我们正在将ASP.NET Core 2.1与.NET Framework 4.6.2结合使用。我们有一个客户需要向我们的Web应用程序发送一个很大程度上嵌套的json结构。当他们进行此调用时,我们将输出以下日志并返回错误: 读取器的MaxDepth超过了32。路径“ super.long.path.to property”,第1行,位置42111。”我浏览了…

ASP.NET MVC 5自定义登录,无需脚手架,数据库优先 - c#

我对asp.net和mvc还是很陌生,所以我正在努力学习尽可能多的知识...为此,我从头开始编写博客网站,但是我对身份验证和授权有些困惑。由于我倾向于不真正使用任何脚手架的东西,所以我首先要使用数据库,所以不希望asp.net身份为我创建表。我对散列和加盐密码很酷,并对照数据库检查用户,我遇到的麻烦是将用户设置为登录状态并检查他们应该能够访问什么。我真的很想…

asp.net oledbcommand返回所有行 - c#

我正在使用Oledbconnection连接到Microsoft Access数据库,并且正在使用OleDbCommand检索一些信息。我在数据库中有一个名为retrieveInfo的查询,该查询检索3行数据。字段中有一些重复项,但是应该是这样。我的数据如下所示: Name Email A [email protected] B [email protected] B C@gmai…

java.net.URI.create异常 - java

java.net.URI.create("http://adserver.adtech.de/adlink|3.0") 抛出java.net.URISyntaxException: Illegal character in path at index 32: http://adserver.adtech.de/adlink|3.0 虽然n…

如何从JSON文件反序列化.NET Core中封装的对象表? - c#

我想从外部API检索足球联赛的集合。来自服务器的响应如下所示:{ "api": { "results": 1496, "leagues": [ { "league_id": 1, ..... 返回的对象由“ api”字段组成,其中包含“结果”和“联盟”。我想反序列化代码并将其映射…