Как запустить что-то в потоке STA?
В моем приложении WPF я делаю асинхронную связь (с сервером). В функции обратного вызова я в итоге создаю объекты InkPresenter из результата с сервера. Для этого требуется, чтобы запущенный поток был STA, чего, по-видимому, в настоящее время нет. Поэтому я получаю следующее исключение:
Невозможно создать экземпляр InkPresenter, определенный в сборке [..]. Вызывающим потоком должен быть STA, поскольку это требуется для многих компонентов пользовательского интерфейса.
В настоящее время мой вызов асинхронной функции выглядит так:
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
[..]
}
4 ответа
Вы можете запустить STA Threads следующим образом:
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, чтобы изменить виджет!
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;
}
Надеюсь это поможет!
Вы можете использовать класс Dipatcher для выполнения вызова метода в потоке пользовательского интерфейса. Dispatcher предоставляет статическое свойство CurrentDispatcher для получения диспетчера потока.
Если ваш объект класса, который создает InkPresenter, создан в UI-потоке, то метод CurrentDispatcher возвращает Dispatcher UI-Thread.
В Dispatcher вы можете вызвать метод BeginInvoke для асинхронного вызова указанного делегата в потоке.
Это должно быть достаточно хорошо, чтобы вызвать его в потоке пользовательского интерфейса. Поэтому используйте BackgroundWorker
и на RunWorkerAsyncCompleted
Затем вы можете сделать создание inkPresenter.
Я только что использовал следующее, чтобы получить содержимое буфера обмена из потока 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();
Это что-то вроде хака, но я бы использовал 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){
}