Подтвердить что ты не робот

Как создать фоновый рабочий поток, установленный в Single Thread Apartment?

Я создаю автоматизированное тестовое приложение. В этой части приложения я работаю на сервере опроса. Он работает путем постоянного опроса веб-сервера, чтобы определить, когда должен запускаться новый автоматический тест (для ночных автоматизированных прогонов нашего графического интерфейса).

Когда сервер опроса видит запрос, он загружает всю необходимую информацию и затем выполняет тестовый прогон в фоновом работнике. Проблема в том, что часть тестового прогона имеет OLE, COM и другие вызовы (например, Clipboard.Clear()), которые встречаются в потоке рабочего фона. При возникновении одного из этих вызовов возникает следующее исключение:

Текущий поток должен быть установлен в режим однопоточной квартиры (STA) до того, как вызовы OLE могут быть сделаны. Убедитесь, что ваша основная функция имеет STAThreadAttribute, отмеченный на нем.

Как я могу пометить фоновый рабочий поток как квартиру с одним потоком? Главный вызов в моей программе .cs, очевидно, уже имеет этот атрибут.

4b9b3361

Ответ 1

Это невозможно, BGW использует поток threadpool. Потоки TP всегда являются MTA, их нельзя изменить. Вам придется использовать обычный поток, вызывать SetApartmentState() перед его запуском. Эта нить также должна перекачать контур сообщения, вызвать Application.Run().

Возможно, вам стоит подумать о вызове этого кода из потока пользовательского интерфейса. Поскольку, по всей вероятности, COM-сервер все равно использует свои методы в потоке пользовательского интерфейса. Маршалинг звонит из рабочего потока в поток STA, который создал COM-сервер, автоматически, COM позаботится об этом.

Или возьмите быка за рога и маршалите себя. Вы можете создать свой собственный поток STA, чтобы дать серверу счастливый дом. Вы найдете код в этом сообщении, обязательно создайте COM-объект в переопределении Initialize().

Ответ 2

BackgroundWorker использует по умолчанию поток ThreadPool, но вы можете переопределить это поведение. Сначала вам нужно определить пользовательский SynchronizationContext:

public class MySynchronizationContext : SynchronizationContext
{
    public override void Post(SendOrPostCallback d, object state)
    {
        Thread t = new Thread(d.Invoke);
        t.SetApartmentState(ApartmentState.STA);
        t.Start(state);
    }
}

И переопределите стандартный SynchronizationContext, как это, прежде чем использовать BackgroundWorker:

   AsyncOperationManager.SynchronizationContext = new MySynchronizationContext();

ПРИМЕЧАНИЕ. Это может иметь эффекты производительности для остальной части вашего приложения, поэтому вы можете ограничить новую реализацию Post (например, используя параметры состояния или d).

Ответ 3

Я не тестировал его, но если вы вызываете форму WinForms, вы должны вернуться к потоку пользовательского интерфейса, и большая часть материала должна работать снова.

BackgroundWorker bgw = new BackgroundWorker();
bgw.DoWork += new DoWorkEventHandler(this.bgw_DoWork);
bgw.RunWorkerAsync();

private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
    // Invoke the UI thread
    // "this" is referring to the Form1, or what ever your form is
    this.Invoke((MethodInvoker)delegate
    {
        Clipboard.GetText();
        // etc etc
    });
}

Ответ 4

Обычно вы устанавливаете его, определяя атрибут [STAThread()] в точке входа (например, Static Main).

Ответ 5

Я использовал идею + Conrad de Wet, и она отлично работала!

Есть одна небольшая проблема с этим кодом, но вам нужно закрыть "this.Invoke....." как с помощью a));

Вот код Conrad de Wet с этим исправлением:

    BackgroundWorker bgw = new BackgroundWorker();
    bgw.DoWork += new DoWorkEventHandler(this.bgw_DoWork);
    bgw.RunWorkerAsync();>

    private void bgw_DoWork(object sender, DoWorkEventArgs e)
    {
        // Invoke the UI thread
        // "this" is referring to the Form1, or what ever your form is
        this.Invoke((MethodInvoker)delegate
        {
            Clipboard.GetText();
            // etc etc
        });
    }