后台线程中的文件对话框

3

在维护代码时,我发现我们的一个后台工作者中出现了无限挂起的情况。该工作者需要访问一个脚本文件。原始代码是这样编写的:如果没有定义脚本文件,则弹出一个文件对话框,以允许用户选择一个脚本文件。它看起来像这样:

private void bgworker_DoWork(object sender, DoWorkEventArgs e)
{
    ... snip ...

    if (String.IsNullOrWhitespace(scriptFile))
    {
         scriptFile = PromptForScript();
    }

    ... snip ...
}

private string PrompForScript()
{
    string script = "";
    OpenFileDialog openDialog = new OpenFileDialog();

    if (openDialog.ShowDialog() == DialogResult.OK)
    {
        script = openDialog.FileName;
    }

    return script;
}

我已经大致了解了MethodInvoker,但几乎所有的调用方法都需要从控件中进行调用。而相关的后台工作是由一个不扩展Control的单独类运行的。我应该使用调用带有bgworker类的表单吗?还是有其他的方法可以中断线程以等待用户输入?


1
值得一提的是,一个设计考虑因素是在运行后台处理程序之前检查文件是否存在(并相应提示用户),假设线程首先由用户交互触发。通常我更喜欢将“表单类型”调用(比如打开对话框)保留在UI(表单)调用的上下文中,将业务逻辑(运行脚本)分开处理。 - Wonko the Sane
虽然不是很优美的解决方案,但我将回调封送到主 UI 线程,将文件名设置为全局变量,然后从我的子类中调用另一个检索全局变量的方法。简而言之,1.) 调用后台工作程序;2.) bgworker 实例化子类对象并调用类中的方法以启动事件;3.) 主类正在监听事件;4.) 在事件处理程序内部,调用包含 InvokeRequired 检查的方法;5.) 在 GUI 线程上从用户获取文件名;6.) 从子类中调用另一个检索文件名值的方法。 - John Bartels
4个回答

3

不建议在后台工作器的DoWork事件处理程序中调用UI。 BackgroundWorker旨在在非UI线程上执行工作,以保持UI响应性。 您应该在使用RunWorkerAsync启动BackgroundWorker对象之前请求任何文件信息。


这通常是我倾向的方向。我是一名非常初级的程序员,正在开发一个相当大的应用程序,所以我有点犹豫是否要过多地改变结构,但在运行线程之前进行检查是有意义的。 - KChaloux

1
你需要做的是在UI线程上捕获SynchronizationContext,并将其传递给后台工作者。BackgroundWorker可以调用Send()(同步,类似于Invoke)和Post()(异步,类似于BeginInvoke)来调用回正确的UI线程。话虽如此,在这种情况下可能没有必要使用BackgroundWorker-常规线程池线程就足够了。
这个(稍作修改)来自http://msmvps.com/blogs/manoj/archive/2005/11/03/74120.aspx的代码块应该能给你一个大致的想法:
private void button1_Click(object sender, EventArgs e)
{
    // Here we are on the UI thread, so SynchronizationContext.Current
    // is going to be a WindowsFormsSynchronizationContext that Invokes properly
    ctx = SynchronizationContext.Current;
    ThreadPool.QueueUserWorkItem(
        // This delegate is going to be invoked on a background thread
        s => {
            // This uses the context captured above to invoke
            // back to the UI without the "messy" referencing 
            // of a particular form
            ctx.Send(s2 =>
            {
               // Interact with your UI here- you are on the UI thread
            },null);
        }
    );
}

0
如果某个表单在另一个使用BGworker的类中启动了一个长时间运行的进程,为什么不让表单(或根据UI架构的Presenter)处理错误状态的处理呢?
也许只需返回一些状态result(或抛出一个非常具体的异常,您可以在UI中处理它)?
让后台工作程序确定是否存在错误,但将处理错误(特别是显示消息框的UI部分)留给上层。
很抱歉这没有更具体的代码,但它可能会因系统架构的不同而有很多不同的方式。

-2

嗯,Form类有一个Invoke方法,因此将表单实例传递给后台工作类应该可以正常工作。


正如问题所述,在这种情况下没有表单类可供调用invoke方法。 - KChaloux
表格可以传递给类。OP刚才说过,该类不是控件,并问是否可以使用该表格。简短的答案是“可以”。 - zmbq
调用Invoke会阻塞后台工作线程,等待UI线程完成操作;如果UI线程以任何方式(如进度或完成)等待BackgroundWorker,则会发生死锁。不建议使用。 - Peter Ritchie

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