Вложенные BackgroundWorkers: RunWorkerCompleted вызывает в неправильном потоке? - PullRequest
4 голосов
/ 24 ноября 2010

Я работаю над асинхронной операцией, которая должна вызывать дальнейшие асинхронные задачи. Я пытаюсь упростить его с помощью BackgroundWorkers, в результате чего один обратный вызов BackgroundWorker's DoWork () вызывает метод, который создает второй BackgroundWorker, например так (за исключением проверки ошибок и всего этого джаза для краткости):

class Class1
{
    private BackgroundWorker _worker = null;

    public void DoSomethingAsync()
    {
        _worker = new BackgroundWorker();
        _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_worker_RunWorkerCompleted);
        _worker.DoWork += new DoWorkEventHandler(_worker_DoWork);
        _worker.RunWorkerAsync();
    }

    void _worker_DoWork(object sender, DoWorkEventArgs e)
    {
        Class2 foo = new Class2();
        foo.DoSomethingElseAsync();
        while(foo.IsBusy) Thread.Sleep(0);  // try to wait for foo to finish.
    }

    void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        // do stuff
    }
}

class Class2
{
    private BackgroundWorker _worker = null;
    Thread _originalThread = null;

    public AsyncCompletedEventHandler DoSomethingCompleted;

    public bool IsBusy { get { return _worker != null && _worker.IsBusy; } }

    public void DoSomethingElseAsync()
    {
        _originalThread = Thread.CurrentThread;

        _worker = new BackgroundWorker();
        _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_worker_RunWorkerCompleted);
        _worker.DoWork += new DoWorkEventHandler(_worker_DoWork);
        _worker.RunWorkerAsync();
    }

    void _worker_DoWork(object sender, DoWorkEventArgs e)
    {
        // do stuff
    }

    void _worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        Debug.Assert(Thread.CurrentThread == _originalThread);  // fails

        // Assuming the above Assert() were excluded, the following event would be raised on the wrong thread.
        if (DoSomethingCompleted != null) DoSomethingCompleted(this, new AsyncCompletedEventArgs(e.Error, e.Cancelled, null));
    }
}

Итак, проблема в том, что я ожидаю, что Class2._Worker_RunWorkerCompleted () будет выполняться в том же потоке, в котором был вызван Class2.DoSomethingElseAsync (). Этого никогда не происходит - вместо этого обратный вызов выполняется в совершенно новом потоке.

Вот мое подозрение: _worker_DoWork () класса 1 никогда не возвращается, что означает, что поток никогда не вернется к слушателю событий, даже если он существует (я подозреваю, что нет). С другой стороны, если _worker_DoWork () вернулось, BackgroundWorker Class1 автоматически завершит работу преждевременно - ему нужно дождаться завершения работы Class2, прежде чем он сможет завершить свою работу.

Это приводит к двум вопросам:

  1. Правильно ли мое подозрение?
  2. Каков наилучший способ вложения таких асинхронных операций? Могу ли я спасти подход BackgroundWorker или есть какой-то другой, более подходящий метод?

Ответы [ 3 ]

5 голосов
/ 24 ноября 2010

Если BackgroundWorker создается в потоке пользовательского интерфейса, DoWork будет выполняться в потоке пула потоков, а RunWorkerCompleted будет выполняться в потоке пользовательского интерфейса.

Если BackgroundWorker создается вфоновый поток (т. е. не поток пользовательского интерфейса) DoWork будет по-прежнему выполняться в потоке пула потоков, а RunWorkerCompleted также будет выполняться в потоке пула потоков.

В вашем случае, поскольку вы не можете выполнить маршалвызов произвольного (пула потоков) потока, вы не сможете гарантировать желаемое поведение, хотя, возможно, захотите взглянуть на System.Threading.SynchronizationContext.

0 голосов
/ 24 ноября 2010

Во-первых, я нигде не вижу, чтобы на самом деле запускался рабочий.Вы можете изменить метод DoSomethingAsync (а также добавить вызов метода DoSomethingElseAsync в Class2)

public void DoSomethingAsync()
{
    _worker = new BackgroundWorker();
    _worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_worker_RunWorkerCompleted);
    _worker.DoWork += new DoWorkEventHandler(_worker_DoWork);
    _worker.RunWorkerAsync(); // add this line to start it
}

Во-вторых, рабочий обработчик (метод _worker_DoWork) не обязательно находится в том же потоке, что и вызовDoSomethingAsync - в этом вся суть фонового работника.т.е. / чтобы работать в другом потоке.То же самое относится к обработчику полного рабочего (метод _worker_RunWorkerCompleted).

Наконец, кажется, не имеет смысла присоединять два разных фоновых рабочих, если только для верхнего уровня (Class1) не требуется выполнение работы Class2тоже.Было бы лучше иметь одного менеджера для каждого фонового работника.

0 голосов
/ 24 ноября 2010

Вы должны использовать ManualResetEvent для связи между потоками:

http://msdn.microsoft.com/en-us/library/system.threading.manualresetevent%28VS.71%29.aspx

...