Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/332.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何正确地处理表单,而不存在从已处理对象上的另一个线程调用调用的风险?_C#_Winforms_Multithreading_Events_Dispose - Fatal编程技术网

C# 如何正确地处理表单,而不存在从已处理对象上的另一个线程调用调用的风险?

C# 如何正确地处理表单,而不存在从已处理对象上的另一个线程调用调用的风险?,c#,winforms,multithreading,events,dispose,C#,Winforms,Multithreading,Events,Dispose,我有一个窗体,它“侦听”在其他地方引发的事件(不在窗体本身上,也不在其子控件上)。事件是由存在的对象引发的,即使在表单被释放之后,这些对象仍然存在,并且可能在创建表单句柄的线程之外的线程中引发,这意味着我需要在事件处理程序中进行调用(例如,显示表单上的更改) 在表单(重写)的Dispose(bool)方法中,我取消订阅了调用此方法时仍可能订阅的所有事件。但是,有时仍然会从一个事件处理程序调用Invoke。我假设这是因为在取消订阅事件之前,事件处理程序刚刚被调用,然后OS将控制权切换到执行的dis

我有一个窗体,它“侦听”在其他地方引发的事件(不在窗体本身上,也不在其子控件上)。事件是由存在的对象引发的,即使在表单被释放之后,这些对象仍然存在,并且可能在创建表单句柄的线程之外的线程中引发,这意味着我需要在事件处理程序中进行调用(例如,显示表单上的更改)

在表单(重写)的
Dispose(bool)
方法中,我取消订阅了调用此方法时仍可能订阅的所有事件。但是,有时仍然会从一个事件处理程序调用Invoke。我假设这是因为在取消订阅事件之前,事件处理程序刚刚被调用,然后OS将控制权切换到执行的dispose方法,然后将控制权返回给调用disposed对象的Invoke方法的处理程序

锁定线程没有帮助,因为调用Invoke将锁定调用线程,直到主线程处理调用的方法。这可能永远不会发生,因为主线程本身可能正在等待调用线程已获取的对象上的锁的释放,从而创建死锁

因此,简而言之,当表单订阅了外部事件(可能在不同线程中引发)时,如何正确地处理表单

下面是一些关键方法目前的情况。这种方法遇到了我上面描述的问题,但我不确定如何纠正它们

这是一个处理模型数据部分更改的事件处理程序:

private void updateData()
{
 if (model != null && model.Data != null)
 {
  model.Data.SomeDataChanged -= new MyEventHandler(updateSomeData);

  model.Data.SomeDataChanged += new MyEventHandler(updateSomeData);
 }
 updateSomeData();
}
这是一个事件处理程序,必须对视图进行更改:

private void updateSomeData()
{
 if (this.InvokeRequired) this.myInvoke(new MethodInvoker(updateSomeData));
 else
 {
  // do the necessary changes
 }
}
以及myInvoke方法:

private object myInvoke(Delegate method)
{
 object res = null;
 lock (lockObject)
 {
  if (!this.IsDisposed) res = this.Invoke(method);
 }
 return res;
}
我对
Dispose(bool)
方法的重写:

protected override void Dispose(bool disposing)
{
 lock (lockObject)
 {
  if (disposing)
  {
   if (model != null)
   {
    if (model.Data != null)
    {
     model.Data.SomeDataChanged -= new MyEventHandler(updateSomeData);
    }
    // unsubscribe other events, omitted for brevity
   }
   if (components != null)
   {
    components.Dispose();
   }
  }
  base.Dispose(disposing);
 }
}
更新(根据Alan的要求):


我从不显式调用Dispose方法,我让框架来完成。到目前为止,死锁只在应用程序关闭时发生。在执行锁定之前,有时在简单关闭窗体时会抛出一些异常。

< P>有两种方法需要考虑。一种是在
表单
中有一个锁定对象,并在锁中发生对
Dispose
BeginInvoke
的内部调用;由于
Dispose
BeginInvoke
都不需要很长时间,因此代码不必等待很长时间才能锁定


另一种方法是声明,由于
控件.BeginInvoke
/
表单.BeginInvoke
中的设计错误,这些方法有时会抛出一个实际上无法预防的异常,如果操作是否发生在已经处理过的表单上并不重要,那么应该简单地将其吞并。

我想为supercat的答案提供一种可能很有趣的补充

首先制作一个初始计数为1的倒计时事件(我们称之为_invoke_counter)。这应该是表单(或控件)本身的成员变量:

按如下方式包装每次使用Invoke/BeginInvoke:

if(_invoke_counter.TryAddCount())
{
    try
    {
        //code using Invoke/BeginInvoke goes here
    }
    finally { _invoke_counter.Signal(); }
}
然后,在您的配置中,您可以执行以下操作:

_invoke_counter.Signal();
_invoke_counter.Wait();
这也允许你做一些其他的好事。CountdownEvent.Wait()函数有一个带超时的重载。也许您只想等待一段时间,让调用函数在终止之前完成。如果预期调用需要很长时间才能完成,还可以使用DoEvents()在循环中执行Wait(100)之类的操作来保持响应。用这种方法你可以达到很多漂亮的效果

这应该可以防止任何奇怪的计时竞争条件类型的问题,而且理解和实现起来相当简单。如果有人看到这种方法有任何明显的问题,我很想听听,因为我在生产软件中使用这种方法


重要提示:确保处理代码位于终结器的线程上(它应该处于“自然”处理中)。如果您尝试从UI线程手动调用Dispose()方法,它将死锁,因为它将卡在_invoke_counter.Wait()上;调用将不会运行,等等。

我在多线程处理时遇到了调用方法的问题,我找到了一个非常有效的解决方案

我想在任务中创建一个循环,更新表单上的标签以进行监视

但是当我关闭窗体窗口时,我的调用抛出了一个异常,因为我的窗体已被释放

以下是我为解决此问题而实现的模式:

class yourClass : Form
{
    private bool isDisposed = false;
    private CancellationTokenSource cts;
    private bool stopTaskSignal = false;
    public yourClass()
    {
        InitializeComponent();
        this.FormClosing += (s, a) =>
        {
            cts.Cancel();
            isDisposed = true;
            if (!stopTaskSignal)
                a.Cancel = true;
        };
    }

    private void yourClass_Load(object sender, EventArgs e)
    {
        cts = new CancellationTokenSource();
        CancellationToken token = cts.Token;

        Task.Factory.StartNew(() =>
        {
            try
            {
                while (true)
                {
                    if (token.IsCancellationRequested)
                    {
                        token.ThrowIfCancellationRequested();
                    }

                    if (this.InvokeRequired)
                    {
                        this.Invoke((MethodInvoker)delegate { methodToInvoke(); });
                    }
                }
            }
            catch (OperationCanceledException ex)
            {
                this.Invoke((MethodInvoker)delegate { stopTaskSignalAndDispose(); });
            }
        }, token);
    }

    public void stopTaskSignalAndDispose()
    {
        stopTaskSignal = true;
        this.Dispose();
    }

    public void methodToInvoke()
    {
        if (isDisposed) return;
        label_in_form.Text = "text";
    }
}
我在调用中执行methodToInvoke(),以从窗体线程更新标签

关闭窗口时,将调用FormClosing事件。我借此机会取消关闭窗口(a.cancel),并调用对象任务的cancel方法来停止线程

然后,我访问throwifcancellationrequest()方法,该方法抛出一个OperationCanceledException,允许退出循环并完成任务

Invoke方法在队列中发送“窗口消息”

Microsoft说:«对于每个创建窗口的线程,操作系统都会为窗口消息创建一个队列。»

因此,我调用了另一个方法,该方法现在将真正关闭窗口,但这次使用Invoke方法确保此消息将是队列的最后一条


然后我用Dispose()方法关闭窗口。

无论您如何尝试切片,这看起来都很痛苦。。。你能用一些关于你为什么需要处理表格的信息来更新这个问题吗?可能有另一种解决方案。检查可调用方法中的diposing,而不是disposed,maybe@TonyHopkinson,我想我不明白。你的意思是我应该在处理程序中执行
if(this.Disposing)
,或者在
myInvoke
方法中?在被调用的方法中,可能重复@NikolaNovak。好吧,让我们假设一下
class yourClass : Form
{
    private bool isDisposed = false;
    private CancellationTokenSource cts;
    private bool stopTaskSignal = false;
    public yourClass()
    {
        InitializeComponent();
        this.FormClosing += (s, a) =>
        {
            cts.Cancel();
            isDisposed = true;
            if (!stopTaskSignal)
                a.Cancel = true;
        };
    }

    private void yourClass_Load(object sender, EventArgs e)
    {
        cts = new CancellationTokenSource();
        CancellationToken token = cts.Token;

        Task.Factory.StartNew(() =>
        {
            try
            {
                while (true)
                {
                    if (token.IsCancellationRequested)
                    {
                        token.ThrowIfCancellationRequested();
                    }

                    if (this.InvokeRequired)
                    {
                        this.Invoke((MethodInvoker)delegate { methodToInvoke(); });
                    }
                }
            }
            catch (OperationCanceledException ex)
            {
                this.Invoke((MethodInvoker)delegate { stopTaskSignalAndDispose(); });
            }
        }, token);
    }

    public void stopTaskSignalAndDispose()
    {
        stopTaskSignal = true;
        this.Dispose();
    }

    public void methodToInvoke()
    {
        if (isDisposed) return;
        label_in_form.Text = "text";
    }
}