У меня есть длительная операция пользовательского интерфейса в моей форме, которая запускается всякий раз, когда запускается событие. Вместо того, чтобы иметь блок пользовательского интерфейса во время выполнения операции, я хотел бы выполнить операцию в другом потоке, прервать этот поток и начать снова, если событие снова сработает.
Однако для безопасного изменения элементов управления в моей форме мне нужно использовать методы Invoke или BeginInvoke формы. Если я это сделаю, то смогу объединить все свои операции с пользовательским интерфейсом в одну функцию, например:
private delegate void DoUIStuffDelegate(Thing1 arg1, Thing2 arg2);
private void doUIStuff(Thing1 arg1, Thing2 arg2)
{
control1.Visible = false;
this.Controls.Add(arg1.ToButton());
...
control100.Text = arg2.ToString();
}
...
private void backgroundThread()
{
Thing1 arg1 = new Thing1();
Thing2 arg2 = new Thing2();
this.Invoke(new DoUIStuffDelegate(doUIStuff), arg1, arg2);
}
Thread uiStuffThread = null;
public void OnEventFired()
{
if (uiStuffThread != null)
uiStuffThread.Abort();
uiStuffThread = new Thread(backgroundThread);
uiStuffThread.Start();
}
но если я это сделаю, то потеряю возможность работать в отдельном потоке. В качестве альтернативы я мог бы поместить каждую из них в отдельную функцию, например:
private delegate void DoUIStuffLine1Delegate();
private delegate void DoUIStuffLine2Delegate(Thing1 arg1);
...
private delegate void DoUIStuffLine100Delegate(Thing2 arg2);
private void doUIStuffLine1()
{
control1.Visible = false;
}
private void doUIStuffLine2()
{
this.Controls.Add(arg1.ToButton());
}
...
private void doUIStuffLine100(Thing2 arg2)
{
control100.Text = arg2.ToString();
}
...
private void backgroundThread()
{
Thing1 arg1 = new Thing1();
Thing2 arg2 = new Thing2();
this.Invoke(new DoUIStuffLine1Delegate(doUIStuffLine1));
this.Invoke(new DoUIStuffLine2Delegate(doUIStuffLine2), arg1);
...
this.Invoke(new DoUIStuffLine100Delegate(doUIStuffLine100), arg2);
}
Thread uiStuffThread = null;
public void OnEventFired()
{
if (uiStuffThread != null)
uiStuffThread.Abort();
uiStuffThread = new Thread(backgroundThread);
uiStuffThread.Start();
}
но это ужасный, неразрешимый беспорядок. Есть ли способ создать поток, который может изменять пользовательский интерфейс и который я могу прервать? Чтобы я мог сделать что-то вроде этого:
private void doUIStuff()
{
Thing1 arg1 = new Thing1();
Thing2 arg2 = new Thing2();
control1.Visible = false;
this.Controls.Add(arg1.ToButton());
...
control100.Text = arg2.ToString();
}
Thread uiStuffThread = null;
public void OnEventFired()
{
if (uiStuffThread != null)
uiStuffThread.Abort();
uiStuffThread = this.GetNiceThread(doUIStuff);
uiStuffThread.Start();
}
без необходимости отключать перекрестные проверки в моей форме? В идеале я хотел бы иметь возможность установить какой-либо атрибут в потоке или методе, который индивидуально обертывал все операции в делегатах, которые затем вызывались в потоке формы.





Во-первых - не отключайте проверки между потоками ... формы имеют сходство потоков ...
Во-вторых, старайтесь не прерывать потоки; это нехорошо - вы должны предпочесть чистое завершение работы (например, отмену, которую поддерживает BackgroundWorker)
Один из вариантов - написать метод-оболочку, который:
Например:
void worker_DoWork(object sender, DoWorkEventArgs e)
{
try {
Action<Action> update = thingToDo =>
{
if (worker.CancellationPending) throw new SomeException();
this.Invoke(thingToDo);
};
//...
string tmp = "abc"; // long running
update(() => this.Text = tmp);
tmp = "def"; // long running
update(() => textbox1.Text = tmp);
} catch (SomeException) {
e.Cancel = true;
}
}
Это все еще немного беспорядочно, но, возможно, чище, чем прерывание потоков слева и справа ...
Вы можете избежать использования Invoke, используя объект SynchronizationContext, который был введен во фреймворке 2. Хорошо, это то же самое, вы заменяете одно на другое, но на самом деле это более эффективно и надежно. В любом случае, кросс-потокам нужны проверки, потому что без этого вы никогда не сможете получить доступ к элементу управления, созданному в другом потоке.
Об этом читайте в: http://www.codeproject.com/KB/cpp/SyncContextTutorial.aspxhttp://codingly.com/2008/08/04/invokerequired-invoke-synchronizationcontext/
Какой-то код, раскрывающий мою идею:
private Thread workerThread;
private AsyncOperation operation;
public event EventHandler SomethingHappened;
public MySynchronizedClass()
{
operation = AsyncOperationManager.CreateOperation(null);
workerThread = new Thread(new ThreadStart(DoWork));
workerThread.Start();
}
private void DoWork()
{
operation.Post(new SendOrPostCallback(delegate(object state)
{
EventHandler handler = SomethingHappened;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}), null);
operation.OperationCompleted();
}
... или PostOperationCompleted прервет операцию?
Он не прерывается, а только завершает асинхронную операцию. Я думаю, вы можете даже опустить его. Другой способ - не использовать AsyncOperationManager, но, как говорят, он лучше: SynchronizationContext syncContext; this.syncContext = SynchronizationContext.Current; this.syncContext.Post (делегат {}, ноль)
Я не хочу ждать завершения операции - я хочу убить ее, как только получу другое событие.
Я не вижу этого ясно. bw.CancelAsync () - единственный вариант, который я вижу. Другой вариант - использовать АОП. Посмотрите этот проект: secure.codeproject.com/KB/cs/AOPInvokeRequired.aspx
АОП выглядит очень интересно, но все же похоже, что он применяется для отдельных функций. Я ищу что-то, что можно применить к каждой строке функции. У меня ужасное чувство, что единственный способ сделать это - проанализировать MSIL и использовать Reflection.emit для создания нового метода.
Я отвечаю вам здесь, потому что комментарии слишком короткие. Я лично использую BackgroundWorker, и когда метод завершается, поток освобождается. В основном то, что вы делаете:
bw = new BackgroundWorkerExtended();
bw.DoWork += (DoWorkEventHandler)work;
bw.WorkerSupportsCancellation = true;
//bw.WorkerReportsProgress = true;
bw.RunWorkerCompleted += (RunWorkerCompletedEventHandler)workCompleted;
//bw.ProgressChanged+=new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerAsync();
Другое дело, если вы используете Post, он будет асинхронным, если вы используете Send, он будет синхронным.
Отменить: bw.CancelAsync ();
Это хорошо, но мне все равно нужно вызывать Invoke (), если я обновляю какие-либо элементы управления записью. Я действительно ищу что-то, что обернет все вызовы, которые необходимо вызвать - возможно, мне нужно взглянуть на динамические методы.
В дополнение к предложению BackgroundWorker и связанному с ним комментарию BackgroundWorker выполняет маршалинг обратного вызова OnProgress обратно в поток пользовательского интерфейса, поэтому вы МОЖЕТЕ обновлять данные оттуда без необходимости выполнять Invoke ().
Я не могу точно сказать, полезно это или нет, и в любом случае это метод, который вы могли бы довольно легко использовать без фонового рабочего.
Я подозреваю, что если фоновой задаче нужно знать так много о форме, которую она выполняет bazillion вызовы, тогда у вас может возникнуть проблема разделения задач, о которой в любом случае стоит подумать.
обратный вызов OnProgress бесполезен для меня - мне все равно придется написать 100 различных функций, по одной для каждой из 100 строк кода.
Итак, чтобы отменить поток, могу ли я снова вызвать operation.Post () с функцией делегата, которая вызвала исключение? Или это подождет, пока не завершится первая операция.