【问题标题】:How to run something in the STA thread?如何在 STA 线程中运行某些东西?
【发布时间】:2019-12-31 23:33:26
【问题描述】:

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

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

目前我的异步函数调用是这样的:

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 年以来我就没有使用过它......
  • 在该方法对我有用之前添加 [STAThread]。谢谢你,埃比罗布! :-)

标签: c# .net wpf sta


【解决方案1】:

您可以像这样启动 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:MethodWhichRequiresSTA 只是一种方法吗?如果我把一个方法放在那里,我的代码将无法编译,因为它需要它的参数
  • 你能提供一个方法MethodWhichRequiresSTA的例子吗?
  • 抱歉,帖子迟了 - 但 GetFooFromAsyncResult 是什么?
【解决方案2】:

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

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

在 Dispatcher 上,您可以调用 BeginInvoke 方法在线程上异步调用指定的委托。

【讨论】:

  • Dispatcher.Invoke 或 BeginInvoke 是要走的路。比公认的解决方案简单得多
  • 应该谨慎使用Dispatcher.CurrentDispatcher,除非您确定您正在运行在处理DispatcherOperations 的UI 线程上。 CurrentDispatcher 将为当前线程创建一个调度程序,如果它还没有,即使当前线程是 MTA。我通常发现在要更新的控件上使用Dispatcher 属性会更好。
【解决方案3】:

在 UI 线程上调用它应该足够好了。因此,使用BackgroundWorkerRunWorkerAsyncCompleted,您就可以创建inkPresenter。

【讨论】:

  • 你是对的。问题是回调不在 UI 线程上运行。 UI 线程是使用 STA 运行的,所以在 UI 线程上运行它应该可以为我解决这个问题。
【解决方案4】:

我刚刚使用以下内容从 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();

【讨论】:

    【解决方案5】:

    这有点 hack,但我会使用 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)
    {
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-17
      • 1970-01-01
      • 1970-01-01
      • 2017-04-16
      • 2019-04-06
      • 2020-01-15
      • 2019-04-08
      • 1970-01-01
      相关资源
      最近更新 更多