C # Winforms Threading: закрытая форма вызывается - PullRequest
5 голосов
/ 25 августа 2010

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

Может выдать исключение, если фоновый поток вызывает 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 ]

5 голосов
/ 25 августа 2010

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

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

2 голосов
/ 25 августа 2010

Я решил эту проблему с синхронизацией для BeginInvoke, используя рекомендацию Ханса Пассанта, чтобы поймать исключение 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);
}
1 голос
/ 25 августа 2010

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

Редактировать: В MSDN есть несколько примеров кода в разделе "Многопоточное программирование с асинхронным шаблоном на основе событий" .

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

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

0 голосов
/ 25 августа 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...