Запустить метод "async" в фоновом потоке
Я пытаюсь запустить метод async из обычного метода:
public string Prop
{
get { return _prop; }
set
{
_prop = value;
RaisePropertyChanged();
}
}
private async Task<string> GetSomething()
{
return await new Task<string>( () => {
Thread.Sleep(2000);
return "hello world";
});
}
public void Activate()
{
GetSomething.ContinueWith(task => Prop = task.Result).Start();
// ^ exception here
}
Исключение составляет:
Запуск не может быть вызван в задачу продолжения.
Что это значит, так или иначе? Как я могу просто запустить мой асинхронный метод в фоновом потоке, отправить результат обратно в поток пользовательского интерфейса?
Edit
Также попробовал Task.Wait
, но ожидание не заканчивается:
public void Activate()
{
Task.Factory.StartNew<string>( () => {
var task = GetSomething();
task.Wait();
// ^ stuck here
return task.Result;
}).ContinueWith(task => {
Prop = task.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());
GetSomething.ContinueWith(task => Prop = task.Result).Start();
}
Ответы
Ответ 1
Чтобы исправить ваш пример:
public void Activate()
{
Task.Factory.StartNew(() =>
{
//executes in thread pool.
return GetSomething(); // returns a Task.
}) // returns a Task<Task>.
.Unwrap() // "unwraps" the outer task, returning a proxy
// for the inner one returned by GetSomething().
.ContinueWith(task =>
{
// executes in UI thread.
Prop = task.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
Это будет работать, но это старая школа.
Современный способ запустить что-то в фоновом потоке и отправить обратно в поток пользовательского интерфейса - использовать Task.Run()
, async
и await
:
async void Activate()
{
Prop = await Task.Run(() => GetSomething());
}
Task.Run
запустит что-то в потоке пула потоков. Когда вы await
что-то, оно автоматически возвращается в контекст выполнения, который запустил его. В этом случае ваш поток пользовательского интерфейса.
Обычно вам не нужно вызывать Start()
. Предпочитают методы async
, Task.Run
и Task.Factory.StartNew
- все из которых автоматически запускают задачи. Продолжения, созданные с помощью await
или ContinueWith
, также запускаются автоматически, когда заканчивается их родитель.
Ответ 2
Обратите внимание, что задачи возвращаются:
- метод async,
- Task.Fatory.StartNew,
- Task.Run,
не может быть запущен! Они уже горячие задачи...
Ответ 3
ПРЕДУПРЕЖДЕНИЕ об использовании FromCurrentSynchronizationContext:
Хорошо, Кори знает, как заставить меня переписать ответ:).
Таким образом, главный виновник - это, прежде всего, FromCurrentSynchronizationContext!
Каждый раз, когда StartNew или ContinueWith запускается в этом планировщике, он запускается в потоке пользовательского интерфейса. Можно подумать:
OK, начните последующие операции с пользовательским интерфейсом, измените некоторые элементы управления, запустите некоторые операции. Но теперь TaskScheduler.Current не является нулевым, и если у какого-либо элемента управления есть некоторые события, это порождает некоторую StartNew, ожидающую, что она будет запущена на ThreadPool, а затем от нее идет не так. UI aps обычно сложны, неловко поддерживать уверенность, что ничто не вызовет другую операцию StartNew, простой пример здесь:
public partial class Form1 : Form
{
public static int Counter;
public static int Cnt => Interlocked.Increment(ref Counter);
private readonly TextBox _txt = new TextBox();
public static void WriteTrace(string from) => Trace.WriteLine($"{Cnt}:{from}:{Thread.CurrentThread.Name ?? "ThreadPool"}");
public Form1()
{
InitializeComponent();
Thread.CurrentThread.Name = "ThreadUI!";
//this seems to be so nice :)
_txt.TextChanged += (sender, args) => { TestB(); };
WriteTrace("Form1"); TestA(); WriteTrace("Form1");
}
private void TestA()
{
WriteTrace("TestA.Begin");
Task.Factory.StartNew(() => WriteTrace("TestA.StartNew"))
.ContinueWith(t =>
{
WriteTrace("TestA.ContinuWith");
_txt.Text = @"TestA has completed!";
}, TaskScheduler.FromCurrentSynchronizationContext());
WriteTrace("TestA.End");
}
private void TestB()
{
WriteTrace("TestB.Begin");
Task.Factory.StartNew(() => WriteTrace("TestB.StartNew - expected ThreadPool"))
.ContinueWith(t => WriteTrace("TestB.ContinueWith1 should be ThreadPool"))
.ContinueWith(t => WriteTrace("TestB.ContinueWith2"));
WriteTrace("TestB.End");
}
}
- Form1: ThreadUI! - ОК
- TestA.Begin: ThreadUI! - ОК
- TestA.End: ThreadUI! - ОК
- Form1: ThreadUI! - ОК
- TestA.StartNew: ThreadPool - OK
- TestA.ContinuWith: ThreadUI! - ОК
- TestB.Begin: ThreadUI! - ОК
- TestB.End: ThreadUI! - ОК
- TestB.StartNew - ожидаемый ThreadPool: ThreadUI! - МОЖЕТ БЫТЬ НЕОЖИДАННО!
- TestB.ContinueWith1 должен быть ThreadPool: ThreadUI! - МОЖЕТ БЫТЬ НЕОЖИДАННО!
- TestB.ContinueWith2: ThreadUI! - ОК