0

我有一个简单的 C# winform 应用程序,我在其中生成了一个新线程来显示另一个 winform。一个过程完成后,我想使用下面的代码关闭该表单。我遇到的问题是,当我调用busyForm.BeginInvoke它时,它绕过了空检查并抛出错误。如何正确关闭另一个线程中的winform?

static Indicator busyForm; 

public static async Task Execute()
        {
            Thread busyIndicatorthread = new Thread(new ThreadStart(()=>FormThread()));
            busyIndicatorthread.SetApartmentState(ApartmentState.STA);
            busyIndicatorthread.Start();
        }
        
           private static void FormThread()
        {
            busyForm = new Indicator();
            busyForm.Closed += (sender2, e2) => busyForm.Dispatcher.InvokeShutdown();
            Dispatcher.Run();
        }

public static Task Execute(){
  
    Thread busyIndicatorthread = new Thread(new ThreadStart(()=>FormThread(hwind)));
            busyIndicatorthread.SetApartmentState(ApartmentState.STA);
            busyIndicatorthread.Start();
            
    // dos some stuff
            
     if (busyForm != null)
        {
            busyForm.BeginInvoke(new System.Action(() => busyForm.Close())); <--- throw null error
            busyForm = null;
        }
}
4

3 回答 3

2

那是因为在调用.Close()方法之前,时间已经过去,不能确定busyForm是否存在。事实上,当new System.Action(() => busyForm.Close()线程启动时,您的主线程可能会转到busyForm = null;.

您可以尝试将 null 移动到辅助线程。

    if (busyForm != null)
    {
       busyForm.BeginInvoke(new System.Action(() =>
       { 
          lock(busyForm){
            busyForm.Close();
            busyForm = null;
          }
       }));         
    }
于 2021-07-05T10:12:44.480 回答
2

几乎没有应用程序启动另一个消息泵来显示通知。这不是必需的。在所有应用程序中,忙碌和进度对话框都是由 UI 线程生成和显示的。可能阻塞的操作在后台执行,例如在后台线程中,或者更好的是,使用async/await和Task.Run。UI 使用事件或回调来更新,例如使用Progress<T>类。

不过,在这种情况下,似乎只需要在长时间运行的任务之前显示一个表单,然后再隐藏它:

public async void btnDoStuff_Async(object sender, EventArgs args)
{
    //Disable controls, display indicator, etc
    btnDoStuff.Enabled=false;
    using var busyForm = new Indicator();
    busyForm.Show();

    try
    {
        var result=await Task.Run(()=> ActuallyDoStuffAndReturnResult());

        //Back in the UI form
        //Do something with the result
    }
    finally
    {
        //Close the busy indicator, re-enable buttons etc.
        busyForm.Close();
        btnDoStuff.Enabled=true;
    }
}

finally块确保 UI 已启用,并且即使在出现错误的情况下也会隐藏繁忙的表单。

20 多年前,一些 Visual Basic 6 应用程序确实启动了另一个 Window 消息泵来充当“服务器”。Visual Basic 6 线程非常古怪,因此人们使用各种技巧来绕过它的限制。

于 2021-07-05T10:55:42.693 回答
1

当您编写此代码时:

        busyForm.BeginInvoke(new System.Action(() => busyForm.Close())); <--- throw null error
        busyForm = null;

它的执行顺序几乎可以肯定是这样的:

        busyForm = null;
        busyForm.Close();

难怪你得到一个空引用异常!

只需在调用中将表单设置为 null。那会解决它。

然而,这样做的正确方法是 Panagiotis Kanavos 建议的。

于 2021-07-05T11:59:52.330 回答