2010-08-25 5 views
5

Следующий код демонстрирует мою дилемму. Код создает фоновый поток, который обрабатывает что-то, а затем вызывает поток пользовательского интерфейса с результатом.C# Winforms Threading: Закрытая форма получает вызванные

Это может вызвать исключение, если фоновый поток вызывает Invoke в форме после закрытия формы. Он проверяет IsHandleCreated перед вызовом Invoke, но форма может закрыться после проверки.

void MyMethod() 
{ 
    // Define background thread 
    Action action = new Action(
     () => 
     { 
      // Process something 
      var data = BackgroundProcess(); 

      // Try to ensure the form still exists and hope 
      // that doesn't change before Invoke is called 
      if (!IsHandleCreated) 
       return; 

      // Send data to UI thread for processing 
      Invoke(new MethodInvoker(
       () => 
       { 
        UpdateUI(data); 
       })); 
     }); 

    // Queue background thread for execution 
    action.BeginInvoke(); 
} 

Одним из решений может быть синхронизация FormClosing и каждого вызова Invoke, но это звучит не очень элегантно. Есть ли более простой способ?

ответ

4

Да, здесь есть гонка. A берет миллисекунду до того, как цель начнет работать. Он будет работать «лучше», если вместо этого вы используете Control.BeginInvoke(), реализация формы Dispose() будет пустить очередь отправки. Но это все равно гонка, хотя она будет очень редко. Ваш код, записанный в фрагменте, не требует Invoke().

Единственное чистое исправление заключается в блокировке события FormClosing и задержка закрытие, пока вы не подтвердите, что фоновый поток завершен и не может быть запущен снова. Нелегко сделать с вашим кодом, так как это требует «завершенного» обратного вызова, чтобы вы могли действительно закрыть форму. BackgroundWorker would be a better mousetrap. Исправление Q & D заключается в том, чтобы поймать ObjectDisposedException, которое BeginInvoke будет поднимать. Учитывая, насколько это будет редко, когда вы используете BeginInvoke(), этот уродливый взлом может быть приемлемым. Вы просто не можете его проверить :)

+0

Это может по крайней мере смягчить его. Мой код в письменном виде не требует Invoke? Эти исключения выбрасываются примерно 1 из 10 раз. Я могу проверить их! :) – drifter

+0

Это не так, не нужно задерживать поток и ждать завершения работы делегата. Вы ничего не делаете * после вызова Invoke. Вы не можете проверить его, как только вы используете BeginInvoke, вам нужно будет открыть и закрыть форму не менее миллиона раз. Будьте уверены, что гонка все еще там, вам нужно поймать ODE. –

+0

Я бы не прочь поймать ObjectDisposedException, но иногда он выдает InvalidOperationException вместо этого («Invoke или BeginInvoke не может быть вызван в элементе управления до тех пор, пока дескриптор окна не будет создан».) Я не могу это понять, если не могу отличить его от других InvalidOperationException в вызываемом коде, не так ли? – drifter

0

Перед тем, как вызывать на нем, вы можете проверить IsDisposed на форме (или любом элементе управления).

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

+1

Проблема в том, что в некоторых местах Invoke бросает ObjectDisposedException, даже если он проверяет IsDisposed непосредственно перед вызовом. – drifter

1

Посмотрите на WindowsFormsSynchronizationContext. Метод Post отправляет вызов вашему UpdateUI делегату в потоке пользовательского интерфейса без необходимости выделенного окна; это позволяет пропустить вызов IsHandleCreated и Invoke.

Редактировать: В MSDN есть примеры кода под номером "Multithreaded Programming with the Event-based Asynchronous Pattern".

Возможно, вам будет проще программировать класс AsyncOperationManager, который находится поверх WindowsFormsSynchronizationContext. В свою очередь, компонент BackgroundWorker построен поверх AsyncOperationManager.

Нить пользовательского интерфейса определяется как тот, на который вы звоните AsyncOperationManager.CreateOperation; вы хотите позвонить CreateOperation в начале MyMethod, когда вы знаете, что находитесь в потоке пользовательского интерфейса, и запишите его возвращаемое значение в локальной переменной.

+0

Мне это очень нравится. Некоторые из моих пользователей упрямы, чтобы обновить .NET 4, но случай подходит для его использования. – drifter

+0

Это класс .NET 2.0. Не решает проблему, Control.Invoke уже использует ее. Post() будет вызывать ObjectDisposedException. –

+0

Но 'WindowsFormsSynchronizationContext' отправляет сообщения через скрытый элемент управления, который закрыт после закрытия формы и непосредственно перед выходом программы. Ваша форма вообще не участвует. –

2

Я решил эту проблему синхронизации для BeginInvoke, используя рекомендацию Hans Passant, чтобы поймать ObjectDisposedException. Пока это работает. Для облегчения этого я создал методы расширения класса Control.

TryBeginInvoke пытается вызвать свой собственный метод управления. Если метод успешно вызван, он проверяет, был ли установлен элемент управления. Если он был удален, он немедленно возвращается; в противном случае он вызывает метод, первоначально переданный как параметр TryBeginInvoke.Код выглядит следующим образом:

public static class ControlExtension 
{ 
    // --- Static Fields --- 
    static bool _fieldsInitialized = false; 
    static InvokeDelegateDelegate _methodInvokeDelegate; // Initialized lazily to reduce application startup overhead [see method: InitStaticFields] 
    static InvokeMethodDelegate _methodInvokeMethod; // Initialized lazily to reduce application startup overhead [see method: InitStaticFields] 

    // --- Public Static Methods --- 
    public static bool TryBeginInvoke(this Control control, Delegate method, params object[] args) 
    { 
     IAsyncResult asyncResult; 
     return TryBeginInvoke(control, method, out asyncResult, args); 
    } 

    /// <remarks>May return true even if the target of the invocation cannot execute due to being disposed during invocation.</remarks> 
    public static bool TryBeginInvoke(this Control control, Delegate method, out IAsyncResult asyncResult, params object[] args) 
    { 
     if (!_fieldsInitialized) 
      InitStaticFields(); 

     asyncResult = null; 

     if (!control.IsHandleCreated || control.IsDisposed) 
      return false; 

     try 
     { 
      control.BeginInvoke(_methodInvokeDelegate, control, method, args); 
     } 
     catch (ObjectDisposedException) 
     { 
      return false; 
     } 
     catch (InvalidOperationException) // Handle not created 
     { 
      return false; 
     } 

     return true; 
    } 

    public static bool TryBeginInvoke(this Control control, MethodInvoker method) 
    { 
     IAsyncResult asyncResult; 
     return TryBeginInvoke(control, method, out asyncResult); 
    } 

    /// <remarks>May return true even if the target of the invocation cannot execute due to being disposed during invocation.</remarks> 
    public static bool TryBeginInvoke(this Control control, MethodInvoker method, out IAsyncResult asyncResult) 
    { 
     if (!_fieldsInitialized) 
      InitStaticFields(); 

     asyncResult = null; 

     if (!control.IsHandleCreated || control.IsDisposed) 
      return false; 

     try 
     { 
      control.BeginInvoke(_methodInvokeMethod, control, method); 
     } 
     catch (ObjectDisposedException) 
     { 
      return false; 
     } 
     catch (InvalidOperationException) // Handle not created 
     { 
      return false; 
     } 

     return true; 
    } 

    // --- Private Static Methods --- 
    private static void InitStaticFields() 
    { 
     _methodInvokeDelegate = new InvokeDelegateDelegate(InvokeDelegate); 
     _methodInvokeMethod = new InvokeMethodDelegate(InvokeMethod); 
    } 
    private static object InvokeDelegate(Control control, Delegate method, object[] args) 
    { 
     if (!control.IsHandleCreated || control.IsDisposed) 
      return null; 

     return method.DynamicInvoke(args); 
    } 
    private static void InvokeMethod(Control control, MethodInvoker method) 
    { 
     if (!control.IsHandleCreated || control.IsDisposed) 
      return; 

     method(); 
    } 

    // --- Private Nested Types --- 
    delegate object InvokeDelegateDelegate(Control control, Delegate method, object[] args); 
    delegate void InvokeMethodDelegate(Control control, MethodInvoker method); 
} 
Смежные вопросы