从后台线程更新UI - 我怎么知道我的GUI没有被处理?

时间:2012-04-11 16:28:55

标签: c# multithreading user-interface dispose

我有一个后台线程,一旦完成就会更新UI。我试图尽可能安全,所以我不打电话给已经处理过的GUI。

void DoInBackground()
{
    try 
    {
       string result = ServerSideProcess();
*       if (!IsDisposed && !Disposing)
*          BeginInvoke(new StringDelegate(UpdateText), result);
    }
    catch (Exception ex)
    {
*       if (!IsDisposed && !Disposing)
*          BeginInvoke(new VoidDelegate(UpdateFailed));
    }
}


void UpdateText(string txt)
{
    if (!IsDisposed && !Disposing)
        textbox1.Text = txt;
}

void UpdateFailed() 
{
    if (!IsDisposed && !Disposing)
        textbox1.Text = "failed to get data from server";
}

override Dispose(bool disposing)
{
    if (disposing)
    {
        if (components != null)
            components.Dispose();
    }
    base.Dispose(disposing);
}

我认为我在GUI方法中足够安全 - 当我在UpdateText(字符串)或UpdateFailed()中时,Dispose()不会被调用,因为它们都在同一个线程中运行,所以我是假设检查IsDisposing并稍后执行就足够了。但是我怎么能确定(*)中的部分不会在中间得到Dispose(),这会导致在处理类上调用BeginInvoke,最终导致应用程序崩溃?

我通过在(*)部分之间添加Thread.Sleep(2000)来测试它,在Thread.Sleep之前和之后放置断点,并移出控件使其在到达BeginInvoke之前获得Dispose()d。结果 - 我的应用程序崩溃了。我怎么知道运行时不会给我这个不幸的上下文切换场景?

4 个答案:

答案 0 :(得分:4)

它看起来像是一个由Backgroundworker免费完全解决的问题。

有什么理由不使用它?

答案 1 :(得分:2)

我称之为异常条件 - 在BeginInvokes周围使用try / catch来捕获并显式处理抛出的异常。

答案 2 :(得分:0)

执行时,以下内容未显示任何错误消息:

BackgroundWorker bw = new BackgroundWorker();

public Form1()
{
    InitializeComponent();

    bw.DoWork += bw_DoWork;
    bw.RunWorkerCompleted += bw_RunWorkerCompleted;

    Shown += Form1_Shown;
}

void Form1_Shown(object sender, EventArgs e)
{
    bw.RunWorkerAsync();
    Thread.Sleep(1000);
    Dispose();
}

void bw_DoWork(object sender, DoWorkEventArgs e)
{
    //This is your second thread.
    Thread.Sleep(2000);
}

void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    //This runs when the second thread is finished. Update here.
    Text = "Done";
}

答案 3 :(得分:0)

在某些情况下,您可以通过让控件的Dispose例程获取锁定,设置标志并释放锁定来实现干净关闭;您的更新例程应获取锁定,如果未设置标志,请执行BeginInvoke,然后释放锁定。锁定的关键目的是确保一旦更新例程决定调用BeginInvoke,控件将不会被释放,直到更新发生。

有人说过,在很多方面我认为只做BeginInvoke更加清晰,并且如果控件从你身下被处理掉,那就会吞下异常。确实应该有一个TryBeginInvoke可以执行BeginInvoke并返回True如果控件处于活动状态,否则返回False,但是唉,没有,也没有无竞赛的方式创建一个没有注入控件的Dispose进程或让异常发生并扼杀它。