【问题标题】:.NET Threading & Locks & Waiting.NET 线程 & 锁 & 等待
【发布时间】:2010-02-17 16:03:03
【问题描述】:

我试图让我的 GUI 线程在长时间运行的操作中保持响应式。这些操作必须是同步的,因为它们通常是在请求的操作完成之前需要完成的操作。

我试图通过后台工作人员、监视器和锁定对象来执行此操作。本质上,我想在启动长时间运行的进程之前启动一个计时器,在后台线程中启动长时间运行的进程并在后台工作线程上等待以表示它已经完成,然后再继续依赖代码。如果长时间运行的过程花费的时间太长,则向用户显示“正在加载...”对话框,以便他们知道应用程序没有崩溃。

这方面的一个例子可能是图形包中的用户单击按钮,必须先从磁盘加载大图像,然后才能绘制图像,然后打印计算到顶部一百万位小数的 pi。

我无法从磁盘异步加载图像,这将使 UI 保持响应,因为用户可能会启动另一个会扰乱程序状态的操作(即撤消操作)。

可以简单地将光标更改为沙漏并完成它,但在许多情况下,我希望用户也能够取消操作 - “正在加载.. ." 带有取消按钮的对话可以很好地解决这个问题。


我最初的目标是使用锁定对象和System.Threading.Monitor.Enter(),以便 UI 线程等到长时间运行的线程完成,然后继续执行。如果计时器在长时间运行的线程完成之前触发,则 UI 线程仍可用于处理事件并在屏幕上绘制对话框。

我遇到的问题是,在 UI 线程尝试获取锁定之前,我无法让后台工作人员锁定对象。

相当烦人的是,我正在使用一些非常黑盒的第 3 方代码来进行处理。因此,我无法将代码定制为线程友好并报告其进度或支持取消。


我的问题

是否有任何行之有效的方法来包装第 3 方代码,以便 UI 线程保持响应,并且如果需要我可以显示取消对话? - 在许多情况下,长时间运行的操作几乎立即完成并且不需要显示对话框。


进一步澄清

我为什么要这样做?异步操作是 Windows 应用程序的宠儿...

好吧,我不想在启动长时间运行的异步操作时锁定用户界面的各个方面,然后在完成后解锁各个方面。我可以 - 通过设置光标或物理禁用所有按钮等,但实际上我更希望能够简单地将调用包装在“某些对象/方法等”中,这将允许弹出一个对话框,如果(并且仅当)操作花费的时间足够长以影响用户。我不必担心执行流程的变化,我仍然(总体上)能够在代码中维护原子操作(而不是在回调中拆分)并且仍然有一个“响应式”用户界面。

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

【问题讨论】:

  • 为什么你不能从磁盘(或其他第三方代码)异步加载图像(例如 BackgroundWorker 执行或你自己的显式线程方法)?它必须在 UI 线程上执行吗?
  • 如果您可以完全避免它,请不要在最终用户注意到的任何时间段内保持同步对象。将它们视为稀缺资源,就像 SQL 事务一样。
  • 顺便说一句,C# 是一种没有线程的编程语言。

标签: c# .net multithreading locking


【解决方案1】:

在您进一步了解之前,我会认真考虑 .NET 中的 BackgroundWorker 类。您说您使用了“后台工作人员”,所以我不确定这是否是您的意思。它具有从您的工作函数中通过进度通知回调您的 UI 的功能。有了进度通知,它应该会大大减少您对同步对象的需求。

【讨论】:

  • 我过去曾多次使用过BackgroundWorkers,但是backgroundWorker 本质上是一个非阻塞异步线程。因此,尽管我尝试过,但我无法将其制作为以同步方式运行。
  • 显示一个模式对话框以报告来自 BackgroundWorker 的进度。这将模拟您正在寻找的同步行为。
【解决方案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();
    }

最后,在 long 方法中:

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

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

这有点粗略,可能有一些错误,但总体思路很简单 - 执行定时 Join() 以仅在操作较长时显示对话框,并从长时间运行的线程本身关闭对话框。

【讨论】:

  • 这听起来很像我的目标。我试试看!
  • 在这里或那里进行一些调整,这正是我想要的!谢谢你
【解决方案3】:

我会将您的工作人员放在一个单独的类中并在它自己的线程上运行它,并使用回调来通知您的 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");
    }
}

【讨论】:

    【解决方案4】:

    我假设这是 Windows 窗体? 一种在实际阻塞时恢复响应的廉价方法是这样的模式:

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

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

    【讨论】:

    • 我同意中止线程通常不是一个好主意,但在某些情况下(即静态方法),您通常可以确保在返回结果之前不会更改任何状态 -因此中止的危险性较小。
    猜你喜欢
    • 1970-01-01
    • 2015-05-24
    • 2017-08-26
    • 2021-10-20
    • 2022-01-03
    • 2018-10-07
    • 1970-01-01
    • 2010-12-07
    • 1970-01-01
    相关资源
    最近更新 更多