我试图让我的GUI线程在长时间运行的操作中保持响应。这些操作必须是同步的,因为它们通常是在请求的操作完成之前需要完成的操作。
我试图用后台工作人员,监视器和锁定对象来做这件事。本质上我想在长时间运行的进程启动之前启动一个计时器,在后台线程中启动长时间运行的进程并等待后台工作线程表示它已经完成,然后再继续使用相关代码。如果长时间运行的过程花费的时间过长,则向用户显示“加载...”对话框,以便他们知道应用程序没有崩溃。
这可能是图形包中的用户点击按钮,在我们绘制图像然后打印pi计算到顶部的一百万个小数位之前,必须先从磁盘加载大图像。
我无法从磁盘异步加载图像,这会使UI保持响应,因为用户可能会启动另一个操作,这会破坏程序状态(即撤消操作)。
我可以简单地将光标更改为沙漏并完成它,但在许多实例中我希望用户也可以取消操作 - “正在加载.. “使用取消按钮进行对话可以很好地解决这个问题。
我最初的目标是使用一个锁对象和System.Threading.Monitor.Enter()
,以便UI线程等待长时间运行的线程完成,然后继续执行。如果计时器在长时间运行的线程完成之前触发,则UI线程仍可用于处理事件并在屏幕上绘制对话框。
我遇到的问题是,在UI线程尝试锁定之前,我无法让后台工作程序锁定对象。
相当恼人地我正在使用一些非常黑盒子的第三方代码进行处理。因此,我不能将代码定制为对线程友好并报告其进度或支持取消。
我的问题
是否有任何经过验证的方式包装第三方代码,以便UI线程保持响应,如果需要,我可以显示取消对话? - 将会有许多实例,其中长时间运行的操作几乎立即完成,并且不需要显示对话框。
进一步澄清
为什么我要这样做?异步操作是Windows应用程序的宠儿......
嗯,我不想在启动长时间运行的异步操作时锁定用户界面的每个方面,然后在完成后解锁每个方面。我可以 - 通过设置光标或物理禁用所有的删除等等,但实际上我更愿意能够简单地将调用包装在'某些对象/方法等'中,这将允许弹出一个对话框(如果仅当)操作花费足够长的时间才能影响用户。我不必担心执行流程的变化,我仍然(总体上)能够在代码中维护原子操作(不会在回调中分开)并且仍然具有“响应”UI。
我可以理解为什么到目前为止我在将BackgroundWorker / Thread制作成同步阻塞线程方面没有成功,但我确实担心我必须在GUI中沿着while(true){ sleep() }
路径走下去线程而不是使用锁。
答案 0 :(得分:6)
在进一步讨论之前,我会认真考虑.NET中的BackgroundWorker类。你说你使用的是“背景工作者”,所以我不确定这是不是你的意思。它具有从工作人员功能中回调您的UI并具有进度通知的功能。通过进度通知,它可以显着减少对同步对象的需求。
答案 1 :(得分: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()
只显示对话框,并从长时间运行的线程本身关闭对话框
答案 2 :(得分: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");
}
}
答案 3 :(得分:0)
我假设这是Windows窗体? 在实际阻止时获得响应能力的廉价方法是这样的模式:
// Method call would be foo.DoWork()
Action f = foo.DoWork;
var asyncresult = f.BeginInvoke(null, null);
while (!asyncresult.IsCompleted)
Application.DoEvents();
在这种情况下,用户仍然可以单击按钮取消操作。请注意,中止线程通常不是一个好主意 - 它会对您的程序可能运行的任何状态造成严重破坏。更好的想法是定期检查某个bool取消字段是否已设置为true,然后在下一个可能的时刻优雅地结束操作。