Добавьте строку nameserver 8.8.8.8
к Вашему /etc/resolv.conf
.
Это устанавливает альтернативные серверы DNS (общедоступные серверы DNS, выполненные Google), в случае, если это - проблема DNS. Это подобно, хотя не идентичный, к этот другой ответ (который был отправлен позже, и дает другой метод для по существу того же реконфигурирования).
Ваш сценарий, как описано, точно соответствует BackgroundWorker
- почему бы просто не использовать это? Ваши требования к решению слишком общие и довольно необоснованные - я сомневаюсь, что есть какое-либо решение, которое бы их всех удовлетворило.
Если вам не нравится BackgroundWoker (как описано @Pavel), вы можете посмотреть эту библиотеку http : //www.wintellect.com/PowerThreading.aspx .
Если я это понимаю, зачем вам когда-либо нужно удалять диалоговое окно прогресса во время работы приложения? Почему бы просто не показать и скрыть его по запросу пользователя? Похоже, это хотя бы немного упростит вашу проблему.
Это не совсем ответ на вторую часть вопроса, но я включу его только для справки:
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.
Я не собираюсь писать для вас исчерпывающее решение, отвечающее всем вашим требованиям, но я предложу перспективу. В целом, однако, я думаю, что вы стремитесь к успеху с этими требованиями.
Архитектура Invoke
/ BeginInvoke
просто выполняет предоставленный делегат в потоке пользовательского интерфейса элемента управления, отправляя его сообщение Windows и сам цикл сообщений выполняет делегата. Конкретная работа этого не имеет значения, но дело в том, что нет особой причины, по которой вы должны использовать эту архитектуру для синхронизации потока с потоком пользовательского интерфейса. Все, что вам нужно, это какой-то другой запущенный цикл, например, в Forms.Timer
или что-то в этом роде, которое отслеживает Queue
для выполнения делегатами и делает это.
Хорошо, через несколько дней я закончил создание решения. Он решает все перечисленные ограничения и задачи в исходной публикации. Использование простое и прямолинейное:
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<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<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; } }
}
}
Если вы видите здесь что-то не так, сообщите мне.
Почему бы просто не скрыть диалог, когда пользователь закрывает его? Это должно работать нормально, если вы не показываете этот диалог модально. (используйте show вместо showdialog). Я считаю, что вы можете сохранить диалог прогресса поверх окна вашего владельца (если вам нужно), передав хост в диалог, когда вы вызываете show.
Вау, длинный вопрос. Я постараюсь организовать свой ответ так, чтобы вы могли поправить меня, если я что-то понял неправильно, хорошо?
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, поэтому вы должны быть в безопасности когда дело доходит до исключений.
Ого, длинный ответ. Если по какой-либо причине я пропустил какое-либо из ваших требований или неправильно понял что-то из ваших слов (английский не мой родной язык, поэтому мы никогда не уверены), пожалуйста, дайте мне знать.
Некоторое время назад я столкнулся с этой проблемой и предложил решение, включающее контексты синхронизации. Решение состоит в том, чтобы добавить метод расширения к SynchronizationContext, который привязывает конкретный делегат к потоку, к которому привязан SynchronizationContext. Он сгенерирует новый делегат, который при вызове будет маршалировать вызов соответствующему потоку, а затем вызовет исходный делегат. Это делает практически невозможным для потребителей делегата вызвать его в неправильном контексте.
Сообщение в блоге на эту тему:
Я пытаюсь организовать все такие сообщения вызова в графический интерфейс как «запустить и забыть» (обработка исключения, которое графический интерфейс может выдать из-за состояния гонки при удалении формы).
Это Таким образом, если он никогда не выполняется, никакого вреда не будет.
Если графическому интерфейсу нужно ответить рабочему потоку, у него есть способ эффективно отменить уведомление. Для простых целей BackgroundWorker уже справляется с этим.
Использование 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