it-swarm.com.ru

Избегать проблем Invoke/BeginInvoke в межпотоковой обработке событий WinForm?

Я все еще страдаю от фоновых потоков в пользовательском интерфейсе WinForm. Зачем? Вот некоторые из проблем:

  1. Очевидно, что самая важная проблема, я не могу изменить элемент управления, если я выполняю в том же потоке, который его создал.
  2. Как вы знаете, Invoke, BeginInvoke и т.д. Не доступны до тех пор, пока не будет создан элемент управления.
  3. Даже после того, как requireInvoke вернет true, BeginInvoke все равно может выбросить ObjectDisposed и даже если он не сгенерирует, он может никогда не выполнить код, если элемент управления разрушается.
  4. Даже после того, как requireInvoke вернет true, Invoke может бесконечно зависать в ожидании выполнения элементом управления, который был удален одновременно с вызовом Invoke.

Я ищу элегантное решение этой проблемы, но прежде чем углубиться в детали того, что я ищу, я подумал, что проясню проблему. Это для того, чтобы взять общую проблему и поставить более конкретный пример. Для этого примера, скажем, мы передаем большие объемы данных через Интернет. Пользовательский интерфейс должен отображать диалоговое окно прогресса для уже выполняемой передачи. Диалог прогресса должен обновляться постоянно и быстро (обновляется от 5 до 20 раз в секунду). Пользователь может закрыть диалоговое окно прогресса в любое время и при необходимости вызвать его снова. И далее, давайте притворимся в качестве аргументов, что, если диалог видим, он должен обрабатывать каждое событие прогресса. Пользователь может нажать «Отмена» в диалоговом окне выполнения и, изменив аргументы события, отменить операцию.

Теперь мне нужно решение, которое будет соответствовать следующему блоку ограничений:

  1. Разрешить рабочему потоку вызывать метод в элементе управления/форме и блокировать/ждать до завершения выполнения.
  2. Разрешить самому диалогу вызывать этот же метод при инициализации или тому подобное (и, следовательно, не использовать invoke).
  3. Не возлагайте бремени реализации на метод обработки или вызывающее событие, решение должно только изменить саму подписку на событие.
  4. Соответствующая обработка блокировки вызывает диалог, который может находиться в процессе удаления. К сожалению, это не так просто, как проверка IsDisposed.
  5. Должна быть в состоянии использоваться с любым типом события (предположим, делегат типа EventHandler)
  6. Не должны переводить исключения в TargetInvocationException.
  7. Решение должно работать с .Net 2.0 и выше

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

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

Обновление № 2: Хорошо, я попытаюсь описать проблему чуть более подробно и посмотреть, что (если что-нибудь) вытряхивает. Следующие свойства, которые позволяют нам определить его состояние, вызывают некоторые опасения ...

  1. Control.InvokeRequired = задокументировано, что возвращает false, если выполняется в текущем потоке или если IsHandleCreated возвращает false для всех родителей . Я обеспокоен реализацией InvokeRequired, которая может либо генерировать исключение ObjectDisposedException, либо потенциально даже создавать дескриптор объекта. А поскольку InvokeRequired может возвращать значение true, когда мы не можем вызвать (Уничтожить в процессе), и он может вернуть значение false, даже если нам может понадобиться использовать invoke (Создать в процессе), этому просто нельзя доверять во всех случаях. Единственный случай, когда я могу увидеть, где мы можем доверять InvokeRequired, возвращающему false, - это когда IsHandleCreated возвращает true как до, так и после вызова (кстати, документы MSDN для InvokeRequired упоминают проверку IsHandleCreated). 

  2. Control.IsHandleCreated = Возвращает true, если для элемента управления был назначен дескриптор; в противном случае false . Хотя IsHandleCreated является безопасным вызовом, он может выйти из строя, если элемент управления находится в процессе воссоздания своего дескриптора. Эта потенциальная проблема, по-видимому, разрешима путем выполнения блокировки (управления) при доступе к IsHandleCreated и InvokeRequired.

  3. Control.Disposing = Возвращает true, если элемент управления находится в процессе удаления.Control.IsDisposed = Возвращает true, если элемент управления был удален . Я подумываю подписаться на событие Disposed и проверить свойство IsDisposed, чтобы определить, завершится ли когда-либо BeginInvoke. Большая проблема здесь заключается в отсутствии блокировки синхронизации во время перехода Disposing -> Disposed. Возможно, что если вы подпишетесь на событие Disposed и после этого убедитесь, что Disposing == false && IsDisposed == false, вы все равно никогда не увидите срабатывание события Disposed. Это связано с тем, что реализация Dispose устанавливает Disposing = false, а затем устанавливает Disposed = true. Это дает вам возможность (хоть и небольшую) читать «Disposing» и «IsDisposed» как ложные на удаленном элементе управления.

  4. ... у меня болит голова :( Надеюсь, информация, приведенная выше, позволит пролить немного света на проблемы тех, у кого возникли эти проблемы. Я ценю ваши запасные мыслительные циклы по этому вопросу. 

Закрытие проблемы ... Ниже приведена последняя часть метода Control.DestroyHandle ():.

if (!this.RecreatingHandle && (this.threadCallbackList != null)) { lock (this.threadCallbackList) { Exception exception = new ObjectDisposedException(base.GetType().Name); while (this.threadCallbackList.Count > 0) { ThreadMethodEntry entry = (ThreadMethodEntry) this.threadCallbackList.Dequeue(); entry.exception = exception; entry.Complete(); } } } if ((0x40 & ((int) ((long) UnsafeNativeMethods.GetWindowLong(new HandleRef(this.window, this.InternalHandle), -20)))) != 0) { UnsafeNativeMethods.DefMDIChildProc(this.InternalHandle, 0x10, IntPtr.Zero, IntPtr.Zero); } else { this.window.DestroyHandle(); }

    public IntPtr get_Handle()
    {
        if ((checkForIllegalCrossThreadCalls && !inCrossThreadSafeCall) && this.InvokeRequired)
        {
            throw new InvalidOperationException(SR.GetString("IllegalCrossThreadCall", new object[] { this.Name }));
        }
        if (!this.IsHandleCreated)
        {
            this.CreateHandle();
        }
        return this.HandleInternal;
    }

if( !this.IsHandleCreated )
    throw;
... do more stuff
PostMessage( this.Handle, ... );

The issue here is that from another thread I can successfully pass through the first if statement, after which the handle is destroyed by the control's thread, thus causing the get of the Handle property to re-create the window handle on my thread. This then can cause an exception to be raised on the original control's thread. This one really has me stumped as there is no way to guard against this. Had they only use the InternalHandle property and tested for result of IntPtr.Zero this would not be an issue.

48
csharptest.net

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

22
Pavel Minaev

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

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

8
JaredPar

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

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
csharptest.net

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

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

2
Adam Robinson

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

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) Остерегайтесь синхронных звонков. Если вы хотите, вы можете заменить использование Invoke вместо BeginInvoke. Это будет блокировать ваш компонент, пока событие не будет обработано. Однако, если в пользовательском интерфейсе вам нужно общаться с чем-то, что является эксклюзивным для потока, в котором находится ваш компонент, у вас могут возникнуть проблемы взаимоблокировки. (Я не знаю, дал ли я понять, пожалуйста, дайте мне знать). У меня были проблемы с исключениями при использовании отражений (TargetInvocationException) и BeginInvoke (когда они запускают другой поток, вы теряете часть трассировки стека), но я не припоминаю, чтобы у меня были большие проблемы с вызовами Invoke, поэтому вам следует быть в безопасности, когда речь заходит об исключениях.

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

1
diogoriba

Это довольно сложный вопрос. Как я упоминаю в комментарии, я не думаю, что это разрешимо с учетом задокументированных ограничений. Вы можете взломать его, учитывая конкретную реализацию .net framework: знание реализации различных функций-членов может помочь вам обмануть, взяв блокировку здесь и там, и зная, что «на самом деле все нормально, вызывать другие функции-члены в другом потоке. "

Итак, мой основной ответ на данный момент - «нет». Ненавижу говорить, что это невозможно, потому что я очень верю в среду .Net. Кроме того, я сравнительно новичок, не изучая фреймворки вообще или CS, но интернет открыт (даже для невежественных людей, таких как я)!

По другой теме аргумент может быть поставлен и хорошо поддержан: «Вам никогда не нужен Invoke, используйте только BeginInvoke, стреляйте и забывайте». Я не буду пытаться поддержать его или даже сказать, что это правильное утверждение, но я скажу, что общая реализация неверна, и представлю рабочую (я надеюсь).

Вот общая реализация (взято из другого ответа здесь):

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

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

Это не потокобезопасно. Компонент мог легко начать вызывать список вызовов непосредственно перед отменой подписки, и только после того, как мы закончим удаление, вызовется обработчик. Суть в том, что не задокументировано, как каждый компонент должен использовать механизм событий в .Net, и, честно говоря, ему вообще не нужно отписываться от вас: как только вы дадите свой номер телефона, никто не потребуется стереть это!

Лучше это:

protected override void OnLoad(System.EventArgs e)
{
    component.Event += new System.EventHandler(myHandler);
}    
protected override void OnFormClosing(FormClosedEventArgs e)
{
    component.Event -= new System.EventHandler(myHandler);
    lock (lockobj)
    {
        closing = true;
    }
}
private void Handler(object a, System.EventArgs e)
{
    lock (lockobj)
    {
        if (closing)
            return;
        this.BeginInvoke(new System.Action(HandlerImpl));
    }
}
/*Must be called only on GUI thread*/
private void HandlerImpl()
{
    this.Hide();
}
private readonly object lockobj = new object();
private volatile bool closing = false;

Пожалуйста, дайте мне знать, если я что-то пропустил.

1
Limited Atonement

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

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

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

1
Chris Chilvers

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

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
Filip Navara

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

   // Homemade Action-style delegates to provide .Net 2.0 compatibility, since .Net 2.0 does not 
   //  include a non-generic Action delegate nor Action delegates with more than one generic type 
   //  parameter. (The DMethodWithOneParameter<T> definition is not needed, could be Action<T> 
   //  instead, but is defined for consistency.) Some interesting observations can be found here:
   //  http://geekswithblogs.net/BlackRabbitCoder/archive/2011/11/03/c.net-little-wonders-the-generic-action-delegates.aspx
   public delegate void DMethodWithNoParameters();
   public delegate void DMethodWithOneParameter<T>(T parameter1);
   public delegate void DMethodWithTwoParameters<T1, T2>(T1 parameter1, T2 parameter2);
   public delegate void DMethodWithThreeParameters<T1, T2, T3>(T1 parameter1, T2 parameter2, T3 parameter3);


   /// <summary>
   /// Class containing support code to use the SynchronizationContext mechanism to dispatch the 
   /// execution of a method to the WinForms UI thread, from another thread. This can be used as an 
   /// alternative to the Control.BeginInvoke() mechanism which can be problematic under certain 
   /// conditions. See for example the discussion here:
   /// http://stackoverflow.com/questions/1364116/avoiding-the-woes-of-invoke-begininvoke-in-cross-thread-winform-event-handling
   ///
   /// As currently coded this works with methods that take zero, one, two or three arguments, but 
   /// it is a trivial job to extend the code for methods taking more arguments.
   /// </summary>
   public class WinFormsHelper
   {
      // An arbitrary WinForms control associated with thread 1, used to check that thread-switching 
      //  with the SynchronizationContext mechanism should be OK
      private readonly Control _thread1Control = null;

      // SynchronizationContext for the WinForms environment's UI thread
      private readonly WindowsFormsSynchronizationContext _synchronizationContext;


      /// <summary>
      /// Constructor. This must be called on the WinForms UI thread, typically thread 1. (Unless 
      /// running under the Visual Studio debugger, then the thread number is arbitrary.)
      ///
      /// The provided "thread 1 control" must be some WinForms control that will remain in 
      /// existence for as long as this object is going to be used, for example the main Form 
      /// control for the application.
      /// </summary>
      /// <param name="thread1Control">see above</param>
      public WinFormsHelper(Control thread1Control)
      {
         _thread1Control = thread1Control;
         if (thread1Control.InvokeRequired)
            throw new Exception("Not called on thread associated with WinForms controls.");

         _synchronizationContext =
                            SynchronizationContext.Current as WindowsFormsSynchronizationContext;
         if (_synchronizationContext == null) // Should not be possible?
            throw new Exception("SynchronizationContext.Current = null or wrong type.");
      }


      // The following BeginInvoke() methods follow a boilerplate pattern for how these methods 
      // should be implemented - they differ only in the number of arguments that the caller wants 
      // to provide.

      public void BeginInvoke(DMethodWithNoParameters methodWithNoParameters)
      {
         _synchronizationContext.Post((object stateNotUsed) =>
         {
            if (!_thread1Control.IsDisposed)
               methodWithNoParameters();
         }, null);
      }


      public void BeginInvoke<T>(DMethodWithOneParameter<T> methodWithOneParameter, T parameter1)
      {
         _synchronizationContext.Post((object stateNotUsed) =>
         {
            if (!_thread1Control.IsDisposed)
               methodWithOneParameter(parameter1);
         }, null);
      }


      public void BeginInvoke<T1, T2>(DMethodWithTwoParameters<T1, T2> methodWithTwoParameters,
                                      T1 parameter1, T2 parameter2)
      {
         _synchronizationContext.Post((object stateNotUsed) =>
         {
            if (!_thread1Control.IsDisposed)
               methodWithTwoParameters(parameter1, parameter2);
         }, null);
      }


      public void BeginInvoke<T1, T2, T3>(DMethodWithThreeParameters<T1, T2, T3> methodWithThreeParameters,
                                          T1 parameter1, T2 parameter2, T3 parameter3)
      {
         _synchronizationContext.Post((object stateNotUsed) =>
         {
            if (!_thread1Control.IsDisposed)
               methodWithThreeParameters(parameter1, parameter2, parameter3);
         }, null);
      }
   }
0
RenniePet

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

0
JMarsch

Использование 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
AMissico

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

0
Kane

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

0
alexD