async/await с параметром continueOnCapturedContext ConfigureAwait и SynchronizationContext для асинхронных продолжений

Я хотел бы сначала написать код, а затем объяснить ситуацию и задать свой вопрос на основании этого:

public partial class MainWindow : Window {

    public MainWindow() {
        InitializeComponent();
    }

    private async void Button_Click_2(object sender, RoutedEventArgs e) {

        var result = await GetValuesAsync();
        Foo.Text += result;
    }

    public async Task<string> GetValuesAsync() {           

        using (var httpClient = new HttpClient()) {

            var response = await httpClient
                .GetAsync("http://www.google.com")
                .ConfigureAwait(continueOnCapturedContext: false);


            // This is the continuation for the httpClient.GetAsync method.
            // We shouldn't get back to sync context here
            // Cuz the continueOnCapturedContext is set to *false*
            // for the Task which is returned from httpClient.GetAsync method
            var html = await GetStringAsync();

            // This is the continuation for the GetStringAsync method.
            // Should I get back to sync context here?
            // Cuz the continueOnCapturedContext is set to *true*
            // for the Task which is returned from GetStringAsync 

            // However, GetStringAsync may be executed in another thread
            // which has no knowledge for the sync context 
            // because the continueOnCapturedContext is set to *false*
            // for the Task which is returned from httpClient.GetAsync method.

            // But, on the other hand, GetStringAsync method also has a 
            // chance to be executed in the UI thread but we shouldn't be
            // relying on that. 
            html += "Hey...";
            Foo.Text = html;

            return html;
        }
    }

    public async Task<string> GetStringAsync() {

        await Task.Delay(1000);
        return "Done...";
    }
}

Это довольно простой пример WPF, который работает на.NET 4.5 и, вероятно, не имеет большого смысла, но это должно помочь мне объяснить мою ситуацию.

У меня есть кнопка на экране, которая имеет событие асинхронного нажатия. Когда вы смотрите на GetValuesAsync код, вы увидите использование await ключевое слово дважды. При первом использовании я установил continueOnCapturedContext параметр Task.ConfigureAwait метод для false, Итак, это означает, что я не обязательно хочу, чтобы мое продолжение выполнялось внутри SynchronizationContext.Current, Все идет нормально.

На втором await использование (с GetStringAsync метод), я не назвал ConfigureAwait метод. Итак, я в основном указал, что хочу вернуться к текущему контексту синхронизации для продолжения GetStringAsync метод. Итак, как вы можете видеть, я пытаюсь установить TextBlock.Text (которое принадлежит UI-потоку) свойство внутри продолжения.

Когда я запускаю приложение и нажимаю кнопку, я получаю исключение, сообщающее мне следующее сообщение:

Вызывающий поток не может получить доступ к этому объекту, потому что другой поток владеет им.

Сначала это не имело смысла для меня, и я подумал, что обнаружил ошибку, но потом я понял, что GetStringAsync может выполняться в другом потоке (весьма вероятно), который отличается от потока пользовательского интерфейса и не имеет сведений о контексте синхронизации, поскольку continueOnCapturedContext установлен в false для Task который возвращается из httpClient.GetAsync метод.

Это тот случай, здесь? Также, в этом случае, есть ли шанс для GetStringAsync метод, который будет отправлен обратно в поток пользовательского интерфейса, поскольку продолжение метода httpClient.GetAsync может быть выполнено внутри потока пользовательского интерфейса?

У меня также есть несколько комментариев внутри кода. Учитывая мои вопросы и комментарии внутри кода, я что-то здесь упускаю?

1 ответ

Решение

Когда вы звоните ConfigureAwait(false)остальная часть метода будет выполняться в потоке пула потоков, если только Task Вы awaitИнг уже завершен.

поскольку GetAsync почти наверняка будет работать асинхронно GetStringAsync работать в потоке пула потоков.

public async Task<string> GetValuesAsync() {           

    using (var httpClient = new HttpClient()) {

        var response = await httpClient
            .GetAsync("http://www.google.com")
            .ConfigureAwait(continueOnCapturedContext: false);

        // And now we're on the thread pool thread.

        // This "await" will capture the current SynchronizationContext...
        var html = await GetStringAsync();
        // ... and resume it here.

        // But it's not the UI SynchronizationContext.
        // It's the ThreadPool SynchronizationContext.
        // So we're back on a thread pool thread here.

        // So this will raise an exception.
        html += "Hey...";
        Foo.Text = html;

        return html;
    }
}

Кроме того, в этом случае существует ли возможность отправки метода GetStringAsync обратно в поток пользовательского интерфейса, поскольку продолжение метода httpClient.GetAsync может выполняться внутри потока пользовательского интерфейса?

Единственный способ GetStringAsync будет работать в потоке пользовательского интерфейса, если GetAsync завершает, прежде чем это на самом деле awaitредактор Весьма маловероятно.

По этой причине я предпочитаю использовать ConfigureAwait(false) для каждого await как только контекст больше не нужен.

Другие вопросы по тегам