如何在STA线程中运行某个东西?

43

在我的WPF应用程序中,我进行一些异步通信(与服务器)。在回调函数中,我需要从服务器结果创建InkPresenter对象。这需要运行线程是STA,而当前线程显然不是STA。因此,我会收到以下异常:

无法创建程序集中定义的“InkPresenter”的实例[..],因为许多UI组件需要调用线程为STA。

目前,我的异步函数调用如下:

public void SearchForFooAsync(string searchString)
{
    var caller = new Func<string, Foo>(_patientProxy.SearchForFoo);
    caller.BeginInvoke(searchString, new AsyncCallback(SearchForFooCallbackMethod), null);
}

我该如何让回调函数 - 用于创建 InkPresenter - 成为STA? 或在新的STA线程中调用XamlReader解析。

public void SearchForFooCallbackMethod(IAsyncResult ar)
{
    var foo = GetFooFromAsyncResult(ar); 
    var inkPresenter = XamlReader.Parse(foo.Xaml) as InkPresenter; // <!-- Requires STA
    [..]
}

一个方法前面的 [STAThread] 到底怎么了?虽然不总是适用,但非常简单。也许直到2011年才出现?我好像自2011年以来就没有再使用过它了... - ebyrob
在方法前添加[STAThread]对我有用。谢谢,ebyrob! :-) - Kim Homann
5个回答

59

您可以通过以下方式启动STA线程:

    Thread thread = new Thread(MethodWhichRequiresSTA);
    thread.SetApartmentState(ApartmentState.STA); //Set the thread to STA
    thread.Start(); 
    thread.Join(); //Wait for the thread to end
唯一的问题是你的结果对象必须以某种方式传递。你可以使用一个私有字段,或者深入研究将参数传递给线程。这里我在一个私有字段中设置了foo数据,并启动STA线程来改变inkpresenter!
private var foo;
public void SearchForFooCallbackMethod(IAsyncResult ar)
{
    foo = GetFooFromAsyncResult(ar); 
    Thread thread = new Thread(ProcessInkPresenter);
    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    thread.Join(); 
}

private void ProcessInkPresenter()
{
    var inkPresenter = XamlReader.Parse(foo.Xaml) as InkPresenter;
}

希望这能帮到你!


谢谢 :) 如果这样做可以解决问题,请告诉我。我们使用这种技术在服务器上生成我们的Xaml控件的PNG图像! - Arcturus
似乎解决了问题,但又遇到了另一个问题。.. 一旦我在这里把所有东西都搞定了,就会标记为已接受.. 谢谢! - stiank81
@Arcturus:MethodWhichRequiresSTA是一个方法吗?如果我在那里放一个方法,我的代码将无法编译,因为它需要它的参数。 - Saher Ahwal
请问您能否提供一个 MethodWhichRequiresSTA 方法的示例? - James
抱歉晚发了这篇文章 - 但是GetFooFromAsyncResult是什么? - curiousity

13

你可以使用 Dispatcher 类在 UI 线程上执行方法调用。Dispatcher 提供静态属性 CurrentDispatcher 来获取线程的调度程序。

如果创建 InkPresenter 的类的对象在 UI 线程上创建,则 CurrentDispatcher 方法返回 UI 线程的 Dispatcher。

你可以在 Dispatcher 上调用 BeginInvoke 方法来异步地调用指定的委托。


1
Dispatcher.Invoke或BeginInvoke是正确的方法。比已接受的解决方案简单得多。 - GameAlchemist
应该谨慎使用 Dispatcher.CurrentDispatcher,除非您确定正在运行处理 DispatcherOperation 的 UI 线程。如果当前线程没有调度程序,则 CurrentDispatcher 将为当前线程创建一个调度程序,即使当前线程是 MTA 也是如此。我通常发现最好使用要更新的控件上的 Dispatcher 属性。 - Brian Reichle

3

这个操作可以在UI线程上调用,因此请使用BackgroundWorker,并在RunWorkerAsyncCompleted事件中创建inkPresenter。


你说得对。问题在于回调没有在UI线程上运行。UI线程是使用STA运行的,因此在UI线程上运行它应该可以解决我的问题。 - stiank81

1

我刚刚使用以下方法从STA线程获取剪贴板内容。想着可能会帮助未来的某个人,所以发了出来...

string clipContent = null;
Thread t = new Thread(
    () =>
    {
        clipContent = Clipboard.GetText();
    });
t.SetApartmentState(ApartmentState.STA);
t.Start();
t.Join();

// do stuff with clipContent

t.Abort();

1

这有点像一个技巧,但我会使用XTATestRunner。因此,你的代码将如下所示:

    public void SearchForFooAsync(string searchString)
    {
        var caller = new Func<string, Foo>(_patientProxy.SearchForFoo);
        caller.BeginInvoke(searchString, new AsyncCallback(SearchForFooCallbackMethod), null);
    }

    public void SearchForFooCallbackMethod(IAsyncResult ar)
    {
        var foo = GetFooFromAsyncResult(ar); 
        InkPresenter inkPresenter;
        new XTATestRunner().RunSTA(() => {
            inkPresenter = XamlReader.Parse(foo.Xaml) as InkPresenter;
        });
    }

作为额外的奖励,可以像这样捕获在STA(或MTA)线程中抛出的异常:
try
{
    new XTATestRunner().RunSTA(() => {
        throw new InvalidOperationException();
    });
}
catch (InvalidOperationException ex)
{
}

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