Как запустить что-то в потоке STA?

В моем приложении WPF я делаю некоторую асинхронную связь (с сервером). В функции обратного вызова я в конечном итоге создаю объекты InkPresenter из результата с сервера. Это требует, чтобы работающий поток был STA, который, по-видимому, в настоящее время отсутствует. Поэтому я получаю следующее исключение:

Невозможно создать экземпляр "InkPresenter", определенный в сборке [..] Вызывающий поток должен быть STA, потому что для этого требуется множество компонентов пользовательского интерфейса.

В настоящее время мой вызов функции async выглядит следующим образом:

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

Как я могу сделать обратный вызов - который будет делать создание InkPresenter - быть STA? Или вызовите синтаксический анализ XamlReader в новом потоке STA.

public void SearchForFooCallbackMethod(IAsyncResult ar)
{
    var foo = GetFooFromAsyncResult(ar); 
    var inkPresenter = XamlReader.Parse(foo.Xaml) as InkPresenter; // <!-- Requires 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;
}

Надеюсь, это поможет!

Ответ 2

Вы можете использовать класс Dispatcher для выполнения вызова метода в потоке пользовательского интерфейса. Dispatcher предоставляет статическое свойство CurrentDispatcher для получения диспетчера потока.

Если ваш объект класса, который создает InkPresenter, создан в UI-потоке, то метод CurrentDispatcher возвращает Dispatcher из UI-Thread.

В Dispatcher вы можете вызвать метод BeginInvoke для асинхронного вызова указанного делегата в потоке.

Ответ 3

Должно быть достаточно, чтобы называть его в потоке пользовательского интерфейса. Поэтому используйте BackgroundWorker и на RunWorkerAsyncCompleted, затем вы можете создать inkPresenter.

Ответ 4

Это немного взломать, но я бы использовал 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){
}

Ответ 5

Я только что использовал следующее, чтобы получить содержимое буфера обмена из потока 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();