Предотвращение горя Invoke/BeginInvoke в перекрестном потоке обработка событий WinForm?

Добавьте строку nameserver 8.8.8.8 к Вашему /etc/resolv.conf.

Это устанавливает альтернативные серверы DNS (общедоступные серверы DNS, выполненные Google), в случае, если это - проблема DNS. Это подобно, хотя не идентичный, к этот другой ответ (который был отправлен позже, и дает другой метод для по существу того же реконфигурирования).

48
задан csharptest.net 4 September 2009 в 00:19
поделиться

11 ответов

Ваш сценарий, как описано, точно соответствует BackgroundWorker - почему бы просто не использовать это? Ваши требования к решению слишком общие и довольно необоснованные - я сомневаюсь, что есть какое-либо решение, которое бы их всех удовлетворило.

22
ответ дан 26 November 2019 в 19:04
поделиться

Если вам не нравится BackgroundWoker (как описано @Pavel), вы можете посмотреть эту библиотеку http : //www.wintellect.com/PowerThreading.aspx .

0
ответ дан 26 November 2019 в 19:04
поделиться

Если я это понимаю, зачем вам когда-либо нужно удалять диалоговое окно прогресса во время работы приложения? Почему бы просто не показать и скрыть его по запросу пользователя? Похоже, это хотя бы немного упростит вашу проблему.

0
ответ дан 26 November 2019 в 19:04
поделиться

Это не совсем ответ на вторую часть вопроса, но я включу его только для справки:

private delegate object SafeInvokeCallback(Control control, Delegate method, params object[] parameters);
public static object SafeInvoke(this Control control, Delegate method, params object[] parameters)
{
    if (control == null)
        throw new ArgumentNullException("control");
    if (control.InvokeRequired)
    {
        IAsyncResult result = null;
        try { result = control.BeginInvoke(new SafeInvokeCallback(SafeInvoke), control, method, parameters); }
        catch (InvalidOperationException) { /* This control has not been created or was already (more likely) closed. */ }
        if (result != null)
            return control.EndInvoke(result);
    }
    else
    {
        if (!control.IsDisposed)
            return method.DynamicInvoke(parameters);
    }
    return null;
}

Этот код должен избегать наиболее распространенных ошибок с Invoke / BeginInvoke и его легко использовать . Просто превратите

if (control.InvokeRequired)
    control.Invoke(...)
else
    ...

в

control.SafeInvoke(...)

Аналогичная конструкция возможна для BeginInvoke.

1
ответ дан 26 November 2019 в 19:04
поделиться

Я не собираюсь писать для вас исчерпывающее решение, отвечающее всем вашим требованиям, но я предложу перспективу. В целом, однако, я думаю, что вы стремитесь к успеху с этими требованиями.

Архитектура Invoke / BeginInvoke просто выполняет предоставленный делегат в потоке пользовательского интерфейса элемента управления, отправляя его сообщение Windows и сам цикл сообщений выполняет делегата. Конкретная работа этого не имеет значения, но дело в том, что нет особой причины, по которой вы должны использовать эту архитектуру для синхронизации потока с потоком пользовательского интерфейса. Все, что вам нужно, это какой-то другой запущенный цикл, например, в Forms.Timer или что-то в этом роде, которое отслеживает Queue для выполнения делегатами и делает это.

2
ответ дан 26 November 2019 в 19:04
поделиться

Хорошо, через несколько дней я закончил создание решения. Он решает все перечисленные ограничения и задачи в исходной публикации. Использование простое и прямолинейное:

myWorker.SomeEvent += new EventHandlerForControl<EventArgs>(this, myWorker_SomeEvent).EventHandler;

Когда рабочий поток вызывает это событие, он обрабатывает требуемый вызов управляющего потока. Это гарантирует, что он не будет зависать на неопределенный срок и будет постоянно генерировать исключение ObjectDisposedException, если он не может выполняться в потоке управления. Я создал другие производные класса, один для игнорирования ошибки, а другой для прямого вызова делегата, если элемент управления недоступен. Работает хорошо и полностью проходит несколько тестов, которые воспроизводят указанные выше проблемы. Есть только одна проблема с решением, которую я не могу предотвратить, не нарушив ограничение №3 выше. Эта проблема является последней (Обновление №4) в описании проблемы, проблемы с потоками в get Handle. Это может вызвать неожиданное поведение в исходном потоке управления, и я регулярно видел, как InvalidOperationException () вызывался при вызове Dispose (), поскольку дескриптор в процессе создания в моем потоке. Чтобы справиться с этим, я обеспечиваю блокировку доступа к функциям, которые будут использовать свойство Control.Handle. Это позволяет форме перегружать метод DestroyHandle и блокировать его до вызова базовой реализации. Если это будет сделано, этот класс должен быть полностью потокобезопасным (насколько мне известно).

public class Form : System.Windows.Forms.Form
{
    protected override void DestroyHandle()
    {
        lock (this) base.DestroyHandle();
    }
}

Вы могли заметить, что ключевым аспектом решения тупиковой ситуации стал цикл опроса. Первоначально я успешно решил тестовые случаи, обработав событие элемента управления для Disposed и HandleDestroyed и используя несколько дескрипторов ожидания. После более внимательного просмотра Я обнаружил, что подписка / отказ от подписки на эти события не является поточно-ориентированной. Поэтому я решил вместо этого опросить IsHandleCreated, чтобы не создавать ненужных конфликтов в событиях потока и тем самым избежать возможности создания состояния мертвой блокировки.

В любом случае, вот решение, которое я придумал:

/// <summary>
/// Provies a wrapper type around event handlers for a control that are safe to be
/// used from events on another thread.  If the control is not valid at the time the
/// delegate is called an exception of type ObjectDisposedExcpetion will be raised.
/// </summary>
[System.Diagnostics.DebuggerNonUserCode]
public class EventHandlerForControl<TEventArgs> where TEventArgs : EventArgs
{
    /// <summary> The control who's thread we will use for the invoke </summary>
    protected readonly Control _control;
    /// <summary> The delegate to invoke on the control </summary>
    protected readonly EventHandler<TEventArgs> _delegate;

    /// <summary>
    /// Constructs an EventHandler for the specified method on the given control instance.
    /// </summary>
    public EventHandlerForControl(Control control, EventHandler<TEventArgs> handler)
    {
        if (control == null) throw new ArgumentNullException("control");
        _control = control.TopLevelControl;
        if (handler == null) throw new ArgumentNullException("handler");
        _delegate = handler;
    }

    /// <summary>
    /// Constructs an EventHandler for the specified delegate converting it to the expected
    /// EventHandler&lt;TEventArgs> delegate type.
    /// </summary>
    public EventHandlerForControl(Control control, Delegate handler)
    {
        if (control == null) throw new ArgumentNullException("control");
        _control = control.TopLevelControl;
        if (handler == null) throw new ArgumentNullException("handler");

        //_delegate = handler.Convert<EventHandler<TEventArgs>>();
        _delegate = handler as EventHandler<TEventArgs>;
        if (_delegate == null)
        {
            foreach (Delegate d in handler.GetInvocationList())
            {
                _delegate = (EventHandler<TEventArgs>) Delegate.Combine(_delegate,
                    Delegate.CreateDelegate(typeof(EventHandler<TEventArgs>), d.Target, d.Method, true)
                );
            }
        }
        if (_delegate == null) throw new ArgumentNullException("_delegate");
    }


    /// <summary>
    /// Used to handle the condition that a control's handle is not currently available.  This
    /// can either be before construction or after being disposed.
    /// </summary>
    protected virtual void OnControlDisposed(object sender, TEventArgs args)
    {
        throw new ObjectDisposedException(_control.GetType().Name);
    }

    /// <summary>
    /// This object will allow an implicit cast to the EventHandler&lt;T> type for easier use.
    /// </summary>
    public static implicit operator EventHandler<TEventArgs>(EventHandlerForControl<TEventArgs> instance)
    { return instance.EventHandler; }

    /// <summary>
    /// Handles the 'magic' of safely invoking the delegate on the control without producing
    /// a dead-lock.
    /// </summary>
    public void EventHandler(object sender, TEventArgs args)
    {
        bool requiresInvoke = false, hasHandle = false;
        try
        {
            lock (_control) // locked to avoid conflicts with RecreateHandle and DestroyHandle
            {
                if (true == (hasHandle = _control.IsHandleCreated))
                {
                    requiresInvoke = _control.InvokeRequired;
                    // must remain true for InvokeRequired to be dependable
                    hasHandle &= _control.IsHandleCreated;
                }
            }
        }
        catch (ObjectDisposedException)
        {
            requiresInvoke = hasHandle = false;
        }

        if (!requiresInvoke && hasHandle) // control is from the current thread
        {
            _delegate(sender, args);
            return;
        }
        else if (hasHandle) // control invoke *might* work
        {
            MethodInvokerImpl invocation = new MethodInvokerImpl(_delegate, sender, args);
            IAsyncResult result = null;
            try
            {
                lock (_control)// locked to avoid conflicts with RecreateHandle and DestroyHandle
                    result = _control.BeginInvoke(invocation.Invoker);
            }
            catch (InvalidOperationException)
            { }

            try
            {
                if (result != null)
                {
                    WaitHandle handle = result.AsyncWaitHandle;
                    TimeSpan interval = TimeSpan.FromSeconds(1);
                    bool complete = false;

                    while (!complete && (invocation.MethodRunning || _control.IsHandleCreated))
                    {
                        if (invocation.MethodRunning)
                            complete = handle.WaitOne();//no need to continue polling once running
                        else
                            complete = handle.WaitOne(interval);
                    }

                    if (complete)
                    {
                        _control.EndInvoke(result);
                        return;
                    }
                }
            }
            catch (ObjectDisposedException ode)
            {
                if (ode.ObjectName != _control.GetType().Name)
                    throw;// *likely* from some other source...
            }
        }

        OnControlDisposed(sender, args);
    }

    /// <summary>
    /// The class is used to take advantage of a special-case in the Control.InvokeMarshaledCallbackDo()
    /// implementation that allows us to preserve the exception types that are thrown rather than doing
    /// a delegate.DynamicInvoke();
    /// </summary>
    [System.Diagnostics.DebuggerNonUserCode]
    private class MethodInvokerImpl
    {
        readonly EventHandler<TEventArgs> _handler;
        readonly object _sender;
        readonly TEventArgs _args;
        private bool _received;

        public MethodInvokerImpl(EventHandler<TEventArgs> handler, object sender, TEventArgs args)
        {
            _received = false;
            _handler = handler;
            _sender = sender;
            _args = args;
        }

        public MethodInvoker Invoker { get { return this.Invoke; } }
        private void Invoke() { _received = true; _handler(_sender, _args); }

        public bool MethodRunning { get { return _received; } }
    }
}

Если вы видите здесь что-то не так, сообщите мне.

7
ответ дан 26 November 2019 в 19:04
поделиться

Почему бы просто не скрыть диалог, когда пользователь закрывает его? Это должно работать нормально, если вы не показываете этот диалог модально. (используйте show вместо showdialog). Я считаю, что вы можете сохранить диалог прогресса поверх окна вашего владельца (если вам нужно), передав хост в диалог, когда вы вызываете show.

0
ответ дан 26 November 2019 в 19:04
поделиться

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

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

protected override void OnLoad()
{
    //...
    component.Event += new EventHandler(myHandler);
}

protected override void OnClosing()
{
    //...
    component.Event -= new EventHandler(myHandler);
}

myHandler будет запускаться каждый раз, когда компонент в другом потоке должен выполнить что-то в пользовательском интерфейсе, например. Кроме того, настройка обработчика событий в OnLoad и отказ от подписки в OnClosing гарантирует, что события будут приниматься / обрабатываться пользовательским интерфейсом только тогда, когда его дескриптор создан и готов к обработке событий. Вы даже не сможете запускать события в этом диалоговом окне, если оно находится в процессе удаления, потому что вы больше не будете подписаны на это событие. Если другое событие запускается, пока одно все еще обрабатывается, оно будет поставлено в очередь.

Вы можете передать всю необходимую информацию в аргументах события: обновляете ли вы прогресс, закрываете ли окно и т. Д.

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

private void myHandler(object sender, EventArgs args)
{
    BeginInvoke(Action(myMethod));
}

Таким образом, вы всегда можете использовать invoke, чтобы убедиться, что вы находитесь в правильном потоке.

3) Остерегайтесь синхронных вызовов. Если хотите, можете заменить use Invoke вместо BeginInvoke. Это заблокирует ваш компонент до тех пор, пока событие не будет обработано. Однако, если в пользовательском интерфейсе вам нужно связаться с чем-то, что является эксклюзивным для потока, в котором живет ваш компонент, у вас могут возникнуть проблемы с тупиком. (Я не знаю, ясно ли я выразился, пожалуйста, дайте мне знать). Я' У меня были проблемы с исключениями при использовании отражения (TargetInvocationException) и BeginInvoke (когда они запускают другой поток, вы теряете часть трассировки стека), но я не помню, чтобы у вас было много проблем с вызовами Invoke, поэтому вы должны быть в безопасности когда дело доходит до исключений.

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

1
ответ дан 26 November 2019 в 19:04
поделиться

Некоторое время назад я столкнулся с этой проблемой и предложил решение, включающее контексты синхронизации. Решение состоит в том, чтобы добавить метод расширения к SynchronizationContext, который привязывает конкретный делегат к потоку, к которому привязан SynchronizationContext. Он сгенерирует новый делегат, который при вызове будет маршалировать вызов соответствующему потоку, а затем вызовет исходный делегат. Это делает практически невозможным для потребителей делегата вызвать его в неправильном контексте.

Сообщение в блоге на эту тему:

8
ответ дан 26 November 2019 в 19:04
поделиться

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

Это Таким образом, если он никогда не выполняется, никакого вреда не будет.

Если графическому интерфейсу нужно ответить рабочему потоку, у него есть способ эффективно отменить уведомление. Для простых целей BackgroundWorker уже справляется с этим.

1
ответ дан 26 November 2019 в 19:04
поделиться

Использование System.ComponentModel.ISynchronizeInvoke удобно при создании System.ComponentModel.Component , например BackgroundWorker . Следующий фрагмент кода показывает, как FileSystemWater обрабатывает события.

    ''' <summary>
    ''' Gets or sets the object used to marshal the event handler calls issued as a result of finding a file in a search.
    ''' </summary>
    <IODescription(SR.FSS_SynchronizingObject), DefaultValue(CType(Nothing, String))> _
    Public Property SynchronizingObject() As System.ComponentModel.ISynchronizeInvoke
        Get
            If (_synchronizingObject Is Nothing) AndAlso (MyBase.DesignMode) Then
                Dim oHost As IDesignerHost = DirectCast(MyBase.GetService(GetType(IDesignerHost)), IDesignerHost)
                If (Not (oHost Is Nothing)) Then
                    Dim oRootComponent As Object = oHost.RootComponent
                    If (Not (oRootComponent Is Nothing)) AndAlso (TypeOf oRootComponent Is ISynchronizeInvoke) Then
                        _synchronizingObject = DirectCast(oRootComponent, ISynchronizeInvoke)
                    End If
                End If
            End If
            Return _synchronizingObject
        End Get
        Set(ByVal Value As System.ComponentModel.ISynchronizeInvoke)
            _synchronizingObject = Value
        End Set
    End Property

    Private _onStartupHandler As EventHandler

    Protected Sub OnStartup(ByVal e As EventArgs)
        If ((Not Me.SynchronizingObject Is Nothing) AndAlso Me.SynchronizingObject.InvokeRequired) Then
            Me.SynchronizingObject.BeginInvoke(_onStartupHandler, New Object() {Me, e})
        Else
            _onStartupHandler.Invoke(Me, e)
        End If
    End Sub
0
ответ дан 26 November 2019 в 19:04
поделиться
Другие вопросы по тегам:

Похожие вопросы: