.NET线程和锁定以及等待

4
我试图使我的GUI线程在长时间运行的操作期间保持响应。这些操作必须是同步的,因为它们通常是必须完成才能完成请求操作的操作。
我试图使用后台工作程序、监视器和锁对象来实现此目的。基本上,我想在长时间运行的过程开始之前启动计时器,在后台线程中启动长时间运行的过程,并等待后台工作线程表示已完成,然后继续执行相关代码。如果长时间运行的过程时间太长,则向用户显示“正在加载…”对话框,以便他们知道应用程序没有崩溃。
这种情况的一个例子可能是用户在图形包中单击按钮,必须从磁盘加载大型图像,然后我们才能在其上绘制图像并打印计算出的一百万位小数的pi值。
我无法将从磁盘加载图像异步化,这会使UI保持响应,因为用户可能启动另一个操作,从而破坏程序状态(即撤消操作)。
我可以简单地将光标更改为沙漏形状并完成它,但在许多情况下,我还希望用户能够取消操作-一个带有取消按钮的“正在加载…”对话框会很好地处理这个问题。
最初我想要的是使用锁对象和System.Threading.Monitor.Enter(),使UI线程等待直到长时间运行的线程完成,然后它继续执行。如果计时器在长时间运行的线程完成之前触发,则UI线程仍然可用于处理事件并在屏幕上绘制对话框框。
我遇到的问题是,在UI线程尝试获取锁之前,我无法让后台工作程序锁定对象。
相当恼人的是,我正在使用一些非常黑盒的第三方代码进行处理。因此,我无法将代码调整为线程友好,并报告其进度或支持取消。
我的问题 是否有任何经过验证的方法来包装第三方代码,以便UI线程保持响应,并且如果需要,我可以显示取消对话框?-有许多情况下,长时间运行的操作几乎立即完成,不需要显示对话框。
稍微澄清一下 为什么我要这样做?异步操作是Windows应用程序的宠儿...
好吧,我不想在启动长时间运行的异步操作时锁定用户界面的每个方面,然后在完成时解锁每个方面。我可以-通过设置光标或物理禁用所有按钮等等,但实际上我更喜欢能够简单地将调用包装在“某个对象/方法等”中,如果(且仅当)操作需要时间长到足以影响用户时,将允许对话框弹出。我不必担心执行流程的更改,我仍然(总体上)能够在代码中维护原子操作(不分割回调),并且仍然具有“响应”UI。

我可以理解为什么我迄今为止在将BackgroundWorker/Thread制作成同步阻塞线程方面不成功,但我担心我将不得不走GUI线程中的while(true){ sleep() }路线,而不是使用锁。


为什么不能将从磁盘(或其他第三方代码)加载图像的过程异步化(例如使用BackgroundWorker执行或自己编写显式线程方法)?它必须在UI线程上执行吗? - Pontus Gagge
1
如果可能的话,请尽量避免在用户能够察觉到的时间段内持有同步对象。将它们视为一种稀缺资源,就像 SQL 事务一样。 - Pontus Gagge
顺便说一下,C#是一种没有线程的编程语言。 - John Saunders
4个回答

6
在你继续之前,我强烈建议你考虑使用.NET中的BackgroundWorker类。你说你用了一个"后台工作者",所以我不确定那是否就是你想要的。它可以在你的工作者函数内部回调你的UI并提供进度通知的功能。有了进度通知,它应该显著减少你需要同步对象的需求。

我过去多次使用了BackgroundWorker,但是由于BackgroundWorker本质上是一个非阻塞异步线程,因此尽管我尝试过,但无法使其以同步方式运行。 - Ash
显示一个模态对话框来报告BackgroundWorker的进度。这将模拟您所寻找的同步行为。 - newdayrising

2
这是我会这样做的:

开始长时间操作的代码:

    private void button1_Click(object sender, EventArgs e)
    {
        Thread thr = new Thread(LongMethod);
        thr.Start();

        // wait for 250 ms. If the thread is not finished, we show a from in a modal way
        // saying something like "please wait for the operation to complete"
        if(!thr.Join(250))
        {
            pleaseWaitForm.Thread = thr;
            pleaseWaitForm.ShowDialog();
        }
    }

然后,在“请稍候”表单中。
    public Thread Thread {get;set;}        

    private void PleasWait_FormClosing(object sender, FormClosingEventArgs e)
    {
        // do not allow closing of the form while thread is running
        if (this.Thread.IsAlive)
            e.Cancel = true;
    }

    public void JoinAndClose(){
        // this is a method that allows closing by waiting for the thread to finish
        this.Thread.Join();
        Close();
    }

最后,使用长方法:

private void LongMethod(){
    // Do some stuff
    Threading.Thread.Sleep(100000);

    // Actually close the "please wait" window
    pleaseWaitForm.Invoke(new Action(()=>pleaseWaitForm.JoinAndClose()))
}

这有点草率,可能存在一些错误,但总体思想很简单 - 使用定时的Join(),只在操作较长时显示对话框,并从长时间运行的线程本身关闭对话框。


这听起来非常符合我的目标。我会试一试! - Ash
通过一些微调,这正是我所寻找的!谢谢。 - Ash

1
我建议将您的工作程序放在一个单独的类中,并在其自己的线程上运行,使用回调函数来向您的用户界面(UI)发出进度和完成信号。UI可以使用回调函数来更新进度条并在工作线程完成时启用其他控件。回调函数还可以返回值,因此您可以使用它来干净地停止工作程序。
这里是一个非常简单的例子:
public delegate void CallbackDelegate(string messageArg);

class Program
{
    static void Main(string[] args)
    {
        Worker worker = new Worker();
        worker.Callback = new CallbackDelegate(WorkerStatus);

        Thread thread = new Thread(new ThreadStart(worker.DoSomething));
        thread.IsBackground = true;
        thread.Start();
        Console.ReadLine(); // wait for enter key
    }

    static void WorkerStatus(string statusArg)
    {
        Console.WriteLine(statusArg);
    }
}

public class Worker
{
    public CallbackDelegate Callback { private get; set; }

    public void DoSomething()
    {
        for (int i = 0; i < 10; i++)
        {
            Callback(i.ToString());
        }
        Callback("done");
    }
}

0

我假设这是Windows窗体应用程序?一种比较便宜的方法,可以在实际阻塞时恢复响应性,就是使用以下模式:

// Method call would be foo.DoWork()
Action f = foo.DoWork;
var asyncresult = f.BeginInvoke(null, null);
while (!asyncresult.IsCompleted)
  Application.DoEvents();

在这种情况下,用户仍然可以点击按钮来取消操作。请注意,通常中止线程并不是一个好主意 - 它会对程序正在运行的任何状态造成严重破坏。更好的方法是定期检查一些布尔类型的 cancel 字段是否被设置为 true,然后在下一个可能的时刻优雅地结束操作。

我同意通常中止线程并不是一个好主意,但在某些情况下(例如静态方法),你通常可以确信在返回结果之前没有状态被改变 - 因此中止线程的风险较小。 - Ash

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接