Думаю, тут ви заплуталися кілька речей. Те, що ви просите, вже можливо, використовуючи System.Threading.Tasks
, async
а await
в C # 5 просто нададуть трохи ситніший синтаксичний цукор для тієї ж функції.
Давайте скористаємося прикладом Winforms - опустіть на форму кнопку та текстове поле і скористайтеся цим кодом:
private void button1_Click(object sender, EventArgs e)
{
Task.Factory.StartNew<int>(() => DelayedAdd(5, 10))
.ContinueWith(t => DelayedAdd(t.Result, 20))
.ContinueWith(t => DelayedAdd(t.Result, 30))
.ContinueWith(t => DelayedAdd(t.Result, 50))
.ContinueWith(t => textBox1.Text = t.Result.ToString(),
TaskScheduler.FromCurrentSynchronizationContext());
}
private int DelayedAdd(int a, int b)
{
Thread.Sleep(500);
return a + b;
}
Запустіть його, і ви побачите, що (а) він не блокує потік користувальницького інтерфейсу і (b) ви не отримуєте звичайну помилку "операція перехресних потоків недійсна" - якщо ви не видалите TaskScheduler
аргумент з останнього ContinueWith
, в в якому випадку ви будете.
Це болотний стандарт продовження стилю проходження . Магія трапляється в TaskScheduler
класі, а саме в екземплярі, отриманому користувачем FromCurrentSynchronizationContext
. Передайте це в будь-яке продовження, і ви скажете йому, що продовження має працювати на тій точці, що називається FromCurrentSynchronizationContext
методом - у цьому випадку потоком інтерфейсу користувача.
Очікачі дещо складніші в тому сенсі, що вони знають, з якої нитки вони почали і на якій нитці потрібно продовжити продовження. Отже, наведений вище код можна записати трохи природніше:
private async void button1_Click(object sender, EventArgs e)
{
int a = await DelayedAddAsync(5, 10);
int b = await DelayedAddAsync(a, 20);
int c = await DelayedAddAsync(b, 30);
int d = await DelayedAddAsync(c, 50);
textBox1.Text = d.ToString();
}
private async Task<int> DelayedAddAsync(int a, int b)
{
Thread.Sleep(500);
return a + b;
}
Ці два повинні виглядати дуже схоже, а насправді вони є дуже схожі. DelayedAddAsync
Тепер метод повертає Task<int>
замість int
, і тому await
просто плескаючи продовження на кожному з них. Основна відмінність полягає в тому, що вона проходить уздовж контексту синхронізації на кожному рядку, тому вам не потрібно робити це явно, як ми це робили в минулому прикладі.
Теоретично відмінності набагато суттєвіші. У другому прикладі кожен рядок button1_Click
методу фактично виконується в потоці інтерфейсу, але сама задача ( DelayedAddAsync
) працює у фоновому режимі. У першому прикладі все працює у фоновому режимі , за винятком призначення, textBox1.Text
яке ми явно приєднали до контексту синхронізації потоку інтерфейсу.
Ось що насправді цікаво await
- той факт, що офіціант здатний запускати та виходити з того самого методу, не блокуючи дзвінки. Ви зателефонували await
, поточний потік повертається до обробки повідомлень, і коли це завершиться, офіціант підбере саме там, де він зупинився, в тій же нитці, в якій він і залишився. Але з точки зору вашого Invoke
/BeginInvoke
контрасту в питанні, я " мені шкода сказати, що ти мав би давно цього перестати робити.
await
це стосується функціональності. Це просто багато синтаксичного цукру для продовження проходження . Можливо, є якісь інші незв’язані вдосконалення WinForms, які повинні допомогти? Це підпадало б під рамки самого .NET, а не конкретно на C #.