避免调用/的BeginInvoke的困境中跨线程WinForm的事件处理? [英] Avoiding the woes of Invoke/BeginInvoke in cross-thread WinForm event handling?

查看:541
本文介绍了避免调用/的BeginInvoke的困境中跨线程WinForm的事件处理?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我还是通过后台线程在WinForm的用户界面的困扰。为什么?这里有一些问题:

I'm still plagued by background threading in a WinForm UI. Why? Here are some of the issues:

  1. 显然是最重要的问题,我不能修改控制,除非我在执行创建它的同一线程。
  2. 如你所知,调用,BeginInvoke的,等不可创建控制之后。
  3. 即使RequiresInvoke返回true,BeginInvoke的仍然可以抛出ObjectDisposed即使它不抛出,它可能永远不会执行code。如果控制被销毁。
  4. 即使RequiresInvoke返回true,调用可以无限期挂起由被设置在同一时间,由于来调用控制等待执行。

我正在寻找一个优雅的解决这个问题,但在此之前,我到了什么,我正在寻找的细节,我想我会澄清的问题。这是拿一般的问题,并提出了更具体的例子后面。对于这个例子,让我们说,我们正在传送更大的在互联网上的数据量。用户界面必须能够表现出进步已经正在进行的传送对话框。进度对话框应不断更新,快速(更新每秒5〜20倍)。用户可以关闭该进程在任何时间对话框,如果需要再次调出。并进一步让pretend为了讨论各种情形,如果对话框是可见的,它必须处理每一个进步活动。用户可以单击取消在进度对话框,并通过修改事件参数,取消操作。

I'm looking for an elegant solution to this problem, but before I get into specifics of what I'm looking for I thought I would clarify the problem. This is to take the generic problem and put a more concrete example behind it. For this example let's say we are transferring larger amounts of data over the internet. The user interface must be able to show a progress dialog for the transfer already in-progress. The progress dialog should update constantly and quickly (updates 5 to 20 times per second). The user can dismiss the progress dialog at any time and recall it again if desired. And further, lets pretend for arguments sake that if the dialog is visible, it must process every progress event. The user can click Cancel on the progress dialog and via modifying the event args, cancel the operation.

现在我需要将适合在约束下面的框里的解决方案:

Now I need a solution that will fit in the following box of constraints:

  1. 允许工作线程调用的方法的控制/表格和块/等待执行完成。
  2. 允许对话框本身在初始化或类似物(并因此不能使用的invoke)来调用此方法相同。
  3. 将实现日处理方法或调用事件没有任何负担,解决的办法只能改变事件订阅本身。
  4. 在妥善处理阻塞调用一个对话框,可能是在释放的过程中。不幸的是,这不是因为检查IsDisposed容易。
  5. 必须能够与任何事件类型(假设类型的事件处理程序的委托)使用
  6. 不得翻译例外TargetInvocationException。
  7. 在该解决方案必须与.net 2.0及更高版本

所以,可以这样解决了上面给出的约束?我已经搜查,并通过无数的博客和讨论挖唉,我还是两手空空。

So, can this be solved given the constraints above? I've searched and dug through countless blogs and discussions and alas I'm still empty handed.

更新:我不知道,这个问题没有简单的答案。我只在本网站几天,我见过一些人有很多经验回答问题。我希望这些人的一个已经解决了这个足以够我不花一周左右将需要建立一个合理的解决方案。

Update: I do realize that this question has no easy answer. I've only been on this site for a couple of days and I've seen some people with a lot of experience answering questions. I'm hoping that one of these individuals has solved this sufficiently enough for me to not spend the week or so it will take to build a reasonable solution.

更新#2:好吧,我要去尝试,并说明问题的更详细一点,看看有什么(如果有的话)摇出。下面的属性,使我们能够确定它的状态,有两件事情引起人们的关注......

Update #2: Ok, I'm going to try and describe the problem in a little more detail and see what (if anything) shakes out. The following properties that allow us to determine it's state have a couple of things raise concerns...

  1. Control.InvokeRequired =记录,以返回false,如果在当前线程或者IsHandleCreated运行返回false所有的父母。 我被有潜力要么抛出的ObjectDisposedException或者甚至可能重新创建对象的句柄的InvokeRequired实施困扰。而且,由于InvokeRequired可以返回true时,我们不能够调用(处置正在进行中),它​​可以返回即使我们可能需要使用的invoke(创建中)假这根本不能被信任在所有情况下。我能看到我们可以相信InvokeRequired返回false的唯一情况是,当IsHandleCreated之前和通话(BTW MSDN文档的InvokeRequired的确提到检查IsHandleCreated)后返回true。

  1. Control.InvokeRequired = Documented to return false if running on current thread or if IsHandleCreated returns false for all parents. I'm troubled by the InvokeRequired implementation having the potential to either throw ObjectDisposedException or potentially even re-create the object's handle. And since InvokeRequired can return true when we are not able to invoke (Dispose in progress) and it can return false even though we might need to use invoke (Create in progress) this simply can't be trusted in all cases. The only case I can see where we can trust InvokeRequired returning false is when IsHandleCreated returns true both before and after the call (BTW the MSDN docs for InvokeRequired do mention checking for IsHandleCreated).

Control.IsHandleCreated =返回true,如果一个句柄已分配给控制;否则为false。 虽然IsHandleCreated是一个安全的调用它可能崩溃,如果控制在重建它的处理过程。出现这一潜在的问题通过执行锁(控制),而访问IsHandleCreated和InvokeRequired是solveable。

Control.IsHandleCreated = Returns true if a handle has been assigned to the control; otherwise, false. Though IsHandleCreated is a safe call it may breakdown if the control is in the process of recreating it's handle. This potential problem appears to be solveable by performing a lock(control) while accessing the IsHandleCreated and InvokeRequired.

Control.Disposing =返回true,如果控制在释放的过程中。

Control.Disposing = Returns true if the control is in the process of disposing.

......我的头好痛:(希望以上信息会流一点光线的问题,任何人有这些麻烦。我AP preciate在这闲暇的思想周期。

... my head hurts :( Hopefully the information above will shed a little more light on the issues for anyone having these troubles. I appreciate your spare thought cycles on this.

就麻烦了闭幕式在...以下是Control.DestroyHandle()方法的后半:

Closing in on the trouble... The following is the later half of the Control.DestroyHandle() method:

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从而preventing进一步调用到BeginInvoke方法(或以上precisely MarshaledInvoke该处理双方的BeginInvoke和调用)。这里的问题是,锁释放上threadCallbackList后一个新条目可以前控制的螺纹归零窗口句柄被插入。这似乎是我看到的情况下,虽然很少,往往不足以阻止释放。

You'll notice the ObjectDisposedException being dispatched to all waiting cross-thread invocations. Shortly following this is the call to this.window.DestroyHandle() which in turn destroys the window and set's it's handle reference to IntPtr.Zero thereby preventing further calls into the BeginInvoke method (or more precisely MarshaledInvoke which handle both BeginInvoke and Invoke). The problem here is that after the lock releases on threadCallbackList a new entry can be inserted before the Control's thread zeros the window handle. This appears to be the case I'm seeing, though infrequently, often enough to stop a release.

更新#4:

抱歉让这个拖上;但是,我认为它值得记录在这里。我已经成功地解决了大部分上述问题,我在一个解决方案,工程缩小研究。我已经打多了一个问题,我很担心,但是到现在为止,还没有看到在最狂野。

Sorry to keep dragging this on; however, I thought it worth documenting here. I've managed to solve most of the problems above and I'm narrowing in on a solution that works. I've hit one more issue I was concerned about, but until now, have not seen 'in-the-wild'.

这问题,是因为有那个写Control.Handle属性的天才:

This issue has to do with the genius that wrote Control.Handle property:

	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;
	}

这本身并没有那么糟糕(不管我对获得{}修改意见);然而,当与InvokeRequired属性或调用/ BeginInvoke方法相结合是坏的。这里是基本流程的调用:

This by itself is not so bad (regardless of my opinions on get { } modifications); however, when combined with the InvokeRequired property or the Invoke/BeginInvoke method it is bad. Here is the basic flow the Invoke:

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

这里的问题是,从另一个线程我能顺利通过第一个if语句,然后把手被控制的线程被破坏,从而导致Handle属性的get在我的线程重新创建的窗口句柄。然后,这可能会导致一个异常被原控制的线程上凸起。这一次真的难倒我,因为没有办法防止这种情况。如果他们只使用InternalHandle属性并测试IntPtr.Zero的结果,这将不再是一个问题。

The issue here is that from another thread I can successfully pass through the first if statement, after which the handle is destroyed by the control's thread, thus causing the get of the Handle property to re-create the window handle on my thread. This then can cause an exception to be raised on the original control's thread. This one really has me stumped as there is no way to guard against this. Had they only use the InternalHandle property and tested for result of IntPtr.Zero this would not be an issue.

推荐答案

您的场景,描述的那样,非常符合的BackgroundWorker - 为什么不使用呢?您的需求的解决方案实在是太通用的,而不合理的 - 我怀疑有一个能够满足他们全部的任何解决方案

Your scenario, as described, neatly fits BackgroundWorker - why not just use that? Your requirements for a solution are way too generic, and rather unreasonable - I doubt there is any solution that would satisfy them all.

这篇关于避免调用/的BeginInvoke的困境中跨线程WinForm的事件处理?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆