C #: метод Invoke никогда не возвращается - PullRequest
3 голосов
/ 01 июля 2011

У меня есть многопоточный вызов, который никогда не возвращается.

Поток работает нормально, пока я не вызову строку таким образом: "owner.Invoke(methInvoker);"

При отладке,Я могу медленно шагать, шаг за шагом, но как только я нажму owner.Invoke ... все кончено!

Control owner;
public event ReportCeProgressDelegate ProgressChanged;

public void ReportProgress(int step, object data) {
  if ((owner != null) && (ProgressChanged != null)) {
    if (!CancellationPending) {
      ThreadEventArg e = new ThreadEventArg(step, data);
      if (owner.InvokeRequired) {
        MethodInvoker methInvoker = delegate { ProgressChanged(this, e); };
        owner.Invoke(methInvoker);
      } else {
        ProgressChanged(this, e);
      }
    } else {
      mreReporter.Set();
      mreReporter.Close();
    }
  }
}

К вашему сведению: Это пользовательский класс, имитирующий класс BackgroundWorker, который недоступенна элементах управления, которые не имеют форм.

Мышление Invoke может не потребоваться, я вручную переместил курсор в отладчике на эту часть кода и попытался вызвать ProgressChanged напрямую, но отладчик VS2010 вызвал перекрестный потокисключение.

РЕДАКТИРОВАТЬ:

Из-за первых 3 комментариев, которые я получил, я хотел обновить с помощью моего ProgressChanged метода:

worker.ProgressChanged += delegate(object sender, ThreadEventArg e) {
  if (progressBar1.Style != ProgressBarStyle.Continuous) {
    progressBar1.Value = 0;
    object data = e.Data;
    if (data != null) {
      progressBar1.Maximum = 100;
    }
    progressBar1.Style = ProgressBarStyle.Continuous;
  }
  progressBar1.Value = e.ProgressPercentage;
};

В первой строке анонимного метода есть точка останова , но она также никогда не получает попадания.

EDIT 2

Вотболее полный список вызовов в ветке:

List<TableData> tList = CollectTablesFromForm();
if (0 < tList.Count) {
  using (SqlCeReporter worker = new SqlCeReporter(this)) {
    for (int i = 0; i < tList.Count; i++) {
      ManualResetEvent mre = new ManualResetEvent(false);
      worker.StartThread += SqlCeClass.SaveSqlCeDataTable;
      worker.ProgressChanged += delegate(object sender, ThreadEventArg e) {
        if (progressBar1.Style != ProgressBarStyle.Continuous) {
          progressBar1.Value = 0;
          object data = e.Data;
          if (data != null) {
            progressBar1.Maximum = 100;
          }
          progressBar1.Style = ProgressBarStyle.Continuous;
        }
        progressBar1.Value = e.ProgressPercentage;
      };
      worker.ThreadCompleted += delegate(object sender, ThreadResultArg e) {
        Cursor = Cursors.Default;
        progressBar1.Visible = false;
        progressBar1.Style = ProgressBarStyle.Blocks;
        if (e.Error == null) {
          if (e.Cancelled) {
            MessageBox.Show(this, "Save Action was Cancelled.", "Save Table " + tList[i].TableName);
          }
        } else {
          MessageBox.Show(this, e.Error.Message, "Error Saving Table " + tList[i].TableName, MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
        mre.Set();
      };
      worker.RunWorkerAsync(tList[i]);
      progressBar1.Value = 0;
      progressBar1.Style = ProgressBarStyle.Marquee;
      progressBar1.Visible = true;
      Cursor = Cursors.WaitCursor;
      mre.WaitOne();
    }
  }
}

Надеюсь, это не излишне!Я ненавижу представлять слишком много информации, потому что тогда я заставляю людей критиковать мой стиль.:)

Ответы [ 2 ]

4 голосов
/ 01 июля 2011
  worker.RunWorkerAsync(tList[i]);
  //...
  mre.WaitOne();

Это гарантированный тупик. Делегат, который вы передаете Control.Begin / Invoke (), может выполняться только тогда, когда поток пользовательского интерфейса простаивает, повторно войдя в цикл обработки сообщений. Ваш поток пользовательского интерфейса не простаивает, он заблокирован при вызове WaitOne (). Этот вызов не может быть завершен, пока ваш рабочий поток не завершится. Ваш рабочий поток не может завершиться, пока не будет завершен вызов Invoke (). Этот вызов не может быть завершен, пока поток пользовательского интерфейса не будет свободен. Город тупик.

Блокировка потока пользовательского интерфейса в корне неправильная вещь. COM требует, чтобы он никогда не блокировался. Вот почему BGW имеет событие RunWorkerCompleted.

1 голос
/ 01 июля 2011

Вероятно, вы заблокировали пользовательский интерфейс и рабочие потоки.Control.Invoke выполняет маршалирование выполнения делегата в потоке пользовательского интерфейса, отправляя сообщение в очередь сообщений потока пользовательского интерфейса, а затем ожидает обработки этого сообщения, что, в свою очередь, означает, что выполнение делегата должно завершиться до возврата Control.Invoke.Но что, если ваш поток пользовательского интерфейса занят чем-то другим, кроме отправки и обработки сообщений?По вашему коду я вижу, что здесь может быть ManualResetEvent.Ваш поток пользовательского интерфейса заблокирован при звонке на WaitOne случайно?Если так, то это определенно может быть проблемой.Так как WaitOne не перекачивает сообщения, он заблокирует поток пользовательского интерфейса, который приведет к тупику, когда из вашего рабочего потока вызывается Control.Invoke.

Если вы хотите, чтобы ваше событие ProgressChanged действовало так, как онос BackgroundWorker тогда вам нужно будет вызвать Control.Invoke, чтобы получить эти обработчики событий в потоке пользовательского интерфейса.Так работает BackgroundWorker.Конечно, у вас нет для имитации класса BackgroundWorker именно в этом отношении, если вы готовы к тому, чтобы вызывающие абоненты выполняли собственный маршалинг при обработке события ProgressChanged.

...