Подтвердить что ты не робот

Запустить метод "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();
}
4b9b3361

Ответ 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! - ОК