Сначала вам нужно использовать Dispatcher.Invoke
, чтобы изменить пользовательский интерфейс из другого потока и сделать это из другого класса, вы можете использовать события.
Затем вы можете зарегистрироваться на это событие (события) в основном классе и отправить изменения в пользовательский интерфейс, а в классе вычислений вы выбрасываете событие, когда хотите уведомить пользовательский интерфейс:
class MainWindow : Window
{
private void startCalc()
{
//your code
CalcClass calc = new CalcClass();
calc.ProgressUpdate += (s, e) => {
Dispatcher.Invoke((Action)delegate() { /* update UI */ });
};
Thread calcthread = new Thread(new ParameterizedThreadStart(calc.testMethod));
calcthread.Start(input);
}
}
class CalcClass
{
public event EventHandler ProgressUpdate;
public void testMethod(object input)
{
//part 1
if(ProgressUpdate != null)
ProgressUpdate(this, new YourEventArgs(status));
//part 2
}
}
UPDATE:
Поскольку это все еще часто посещаемый вопрос и ответ, я хочу обновить этот ответ, указав, как бы я это делал сейчас (с .NET 4.5) - это немного дольше, так как я покажу некоторые другие возможности:
class MainWindow : Window
{
Task calcTask = null;
void buttonStartCalc_Clicked(object sender, EventArgs e) { StartCalc(); } // #1
async void buttonDoCalc_Clicked(object sender, EventArgs e) // #2
{
await CalcAsync(); // #2
}
void StartCalc()
{
var calc = PrepareCalc();
calcTask = Task.Run(() => calc.TestMethod(input)); // #3
}
Task CalcAsync()
{
var calc = PrepareCalc();
return Task.Run(() => calc.TestMethod(input)); // #4
}
CalcClass PrepareCalc()
{
//your code
var calc = new CalcClass();
calc.ProgressUpdate += (s, e) => Dispatcher.Invoke((Action)delegate()
{
// update UI
});
return calc;
}
}
class CalcClass
{
public event EventHandler<EventArgs<YourStatus>> ProgressUpdate; // #5
public TestMethod(InputValues input)
{
//part 1
ProgressUpdate.Raise(this, status); // #6 - status is of type YourStatus
//part 2
}
}
static class EventExtensions
{
public static void Raise<T>(this EventHandler<EventArgs<T>> theEvent,
object sender, T args)
{
if (theEvent != null)
theEvent(sender, new EventArgs<T>(args));
}
}
@ 1) Как запустить «синхронные» вычисления и запустить их в фоновом режиме
@ 2) Как запустить его «асинхронно» и «ожидать»: здесь вычисление выполняется и завершается до возврата метода, но из-за async
/ await
пользовательский интерфейс не блокируется ( Кстати: такие обработчики событий являются единственными допустимыми значениями async void
, так как обработчик событий должен возвращать void
- используйте async Task
во всех других случаях )
@ 3) Вместо нового Thread
мы теперь используем Task
. Чтобы позже иметь возможность проверить его (успешное) завершение, мы сохраняем его в глобальном элементе calcTask
. В фоновом режиме это также запускает новый поток и выполняет действие там, но это намного проще в обработке и имеет некоторые другие преимущества.
@ 4) Здесь мы также запускаем действие, но на этот раз мы возвращаем задачу, поэтому «обработчик асинхронных событий» может «ожидать его». Мы также могли бы создать async Task CalcAsync()
, а затем await Task.Run(() => calc.TestMethod(input)).ConfigureAwait(false);
(к вашему сведению: ConfigureAwait(false)
- это чтобы избежать взаимных блокировок, вы должны прочитать об этом, если вы используете async
/ await
, как это было бы много, чтобы объяснить здесь) что приведет к тому же рабочему процессу, но поскольку Task.Run
является единственной «ожидаемой операцией» и последней, мы можем просто вернуть задачу и сохранить один переключатель контекста, что экономит некоторое время выполнения.
@ 5) Здесь я теперь использую «строго типизированное универсальное событие», чтобы мы могли легко передавать и получать наш «объект статуса»
@ 6) Здесь я использую расширение, определенное ниже, которое (помимо простоты использования) решает возможные условия гонки в старом примере. Там могло произойти, что событие получило null
после проверки if
, но перед вызовом, если бы обработчик события был удален в другом потоке именно в этот момент. Этого не может быть, поскольку расширения получают «копию» делегата события, и в той же ситуации обработчик все еще регистрируется в методе Raise
.