如何在没有 UI 线程或任何由 UI 创建的对象的情况下,从非 UI 线程获取 UI 同步上下文?

3
我正在一个非 UI 线程上,需要创建并显示 SaveDialog。但是当我尝试调用 ShowDialog() 方法时,出现如下异常:
"An unhandled exception of type 'System.Threading.ThreadStateException' occurred in System.Windows.Forms.dll"

我没有在 UI 上创建任何对象,那么我如何在 UI 线程上调用此 SaveDialog?是否有全局的 UI SynchronizationContext 可以使用?
编辑:如果我有一个窗体/控件对象来调用 invoke/begininvoke,或者在 UI 线程上使用 SynchronizationContext.Current 引用,则知道如何执行此操作。但是我没有任何这些东西。

你为什么认为这里涉及到了“SynchronizationContext”?我认为问题出在你的“Thread”上,你必须在调用“Start()”之前对它进行一些处理。 - King King
1
如果您想在另一个线程上执行委托(例如当您想从后台线程更新UI控件时),则必须具有将其发布到UI线程的消息队列的机制。在.NET中,这就是“同步上下文”的概念。在此处阅读更多关于此主题的信息:http://msdn.microsoft.com/en-us/magazine/gg598924.aspx另一个选择可能是使用asyncawait关键字进行异步编程,如果您正在使用.NET 4.0或4.5。(http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx) - feO2x
2个回答

3
在.NET中,您需要使用SynchronizationContext类,并将其实例传递给您的方法,就像这样。
public void DoSomeStuffOnBackgroundThread(SynchronizationContext synchronizationContext)
{
    // Do some stuff here
    // ...

    // Show the dialog on the UI thread
    var dialog = new SaveFileDialog();
    synchronizationContext.Send(() => dialog.Show());

    // Send is performed synchronously, thus this line of code only executes when the dialog was closed. You can extract the file name here
    var fileName = dialog.FileName;
}

同步上下文是在Windows Forms框架启动时构建的。因此,当您的应用程序和UI消息循环启动时,在UI线程上简单地调用SynchronizationContext.Current即可获取实例,您可以将该实例传递给方法或对象。
最后,我强烈建议将此功能封装在一个接口后面,并且实现此接口的一个类获取对同步上下文的引用。这样,您的生产代码就不会受到这些不必要的细节,如线程亲和性的污染。
如果您有问题,请随时留言。

1
谢谢feO2x,但问题在于该线程没有与UI相关的引用。我想知道是否有一些包含UI同步上下文的全局变量。 - Pedro77
你如何创建线程?你使用线程池中的一个线程吗?还是使用线程类或任务并行库?无论哪种方式,当开始操作时,只需将“同步上下文”传递给类构造函数或调用非UI线程上方法即可。这不是在需要时获取“同步上下文”,而是在开始时设置所有内容,以便轻松访问它。背后的模式是控制反转/依赖注入。 - feO2x

2

我处理这个问题的方式是将UI线程的TaskScheduler传递到将要在后台线程上运行的对象中。然后,每当后台线程需要在UI线程上执行某些操作时,我使用Task.Factory.StartNew()并使用我给对象的TaskScheduler。举个例子:

using System.Threading.Tasks;

public class BackgroundThread {
    IView _view;
    TaskScheduler _uiThreadScheduler;

    // This object is constructed on the main thread. The main thread's TaskScheduler
    // can be acquired with TaskScheduler.FromCurrentSynchronizationContext()
    public BackgroundThread(IView view, TaskScheduler uiThreadScheduler){
        this._view = view;
        this._uiThreadScheduler = uiThreadScheduler;
    }

    public void DoWork(){
        // Code in this function executes on a background thread
        //...
        string filename;
        var task = Task.Factory.StartNew(() =>
        {
            filename = _view.GetSaveFilename();
        }, CancellationToken.None, TaskCreationOptions.None, _uiThreadScheduler);

        task.Wait();

        // filename now has input from the user, or is null
    }

}

通过将UI的实现隐藏在IView接口后面,后台线程不依赖于任何特定的UI实现。

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