Всегда ли ConfigureAwait(true) возвращается в исходный поток, даже если метод вызываемого объекта внутренне выполняет ConfigureAwait(false)?

Я ожидал вернуться к теме № 1 в местоположении 1.2, но не сделал этого. Есть ли способ вернуться к потоку пользовательского интерфейса после выполнения асинхронного вызова? Спасибо

Также я не могу сделать метод верхнего уровня асинхронным. Не уверен, что Async полностью решит эту проблему, но сейчас у меня нет такого выбора.

class Program
{
    static void Main(string[] args)
    {
        ComputeThenUpdateUI().Wait();
    } 
    static async Task ComputeThenUpdateUI()
    {
        Console.WriteLine($"1.1 {Thread.CurrentThread.ManagedThreadId}");
        await ExpensiveComputingNonUI().ConfigureAwait(true);
        Console.WriteLine($"1.2 {Thread.CurrentThread.ManagedThreadId}");
    } 
    static async Task ExpensiveComputingNonUI()
    {
        Console.WriteLine($"2.1 {Thread.CurrentThread.ManagedThreadId}");
        await Task.Delay(3000).ConfigureAwait(false);
        Console.WriteLine($"2.2 {Thread.CurrentThread.ManagedThreadId}");
        await Task.Delay(3000).ConfigureAwait(false);
        Console.WriteLine($"2.3 {Thread.CurrentThread.ManagedThreadId}");
    }
}
 
 
Output:
1.1 1
2.1 1
2.2 4
2.3 4
1.2 4

2 ответа

Re: не .ConfigureAwait(true) означает, что поток вернется в тот же поток после завершения ожидания?

Нет, в большинстве случаев нет никаких гарантий. Стивен Клири имеет однозначный ответ:

Этот "контекст" является SynchronizationContext.Current, если он не равен нулю, и в этом случае это TaskScheduler.Current. (Если нет выполняемой в данный момент задачи, то TaskScheduler.Current совпадает с TaskScheduler.Default, планировщиком пула потоков).

Важно отметить, что SynchronizationContext или TaskScheduler не обязательно подразумевает определенный поток. Интерфейс SynchronizationContext пользовательского интерфейса будет планировать работу потока пользовательского интерфейса; но ASP.NET SynchronizationContext не будет планировать работу для определенного потока.

т.е. только вызовы, сделанные из потока пользовательского интерфейса, например, WPF или Windows Forms, гарантированно вернутся в тот же поток. Если вам действительно нужно вернуться к тому же потоку, вам нужно будет выполнить настройку, чтобы гарантировать продолжение продолжения в исходном потоке.

Если это не является существенным (например, для потоков пользовательского интерфейса), возврат к одному и тому же потоку обычно нежелателен, поскольку это может снизить производительность (если существует конфликт для потока) и привести к взаимоблокировкам

Re: не могу сделать основной метод верхнего уровня асинхронным

Теперь это возможно в C# 7.1:

public static async Task Main(string[] args)
{
    // Can await asynchronous tasks here.
}

ConfigureAwait это локальное решение, в каждом методе, который вы пишете. ConfigureAwait(true) это способ указать, что вы хотите попытаться вернуться в тот же контекст, в котором был запущен ваш метод при его первом вводе.

Во-первых, это означает, что если вы не знаете, в каком контексте вы изначально работали, то нет смысла пытаться вернуться в тот же контекст. Вы не должны пытаться делать что-либо, зависящее от контекста, если вас могут вызывать в разных контекстах. Вот почему это обычно хороший совет для использования ConfigureAwait(false) если вы создаете библиотеку. Вы не будете знать контексты, в которых вызывается ваш код.

А во-вторых, это означает, что нет "глобального" контекста, к которому можно вернуться. Если ваш абонент (или любой из его вызывающих абонентов и т. Д. Выше по цепочке) уже отделился от своего первоначального контекста вызова, нет способа (с помощью этого механизма) вернуться в этот контекст.

Если вы хотите переключиться на определенный контекст (например, поток пользовательского интерфейса), и это не обязательно будет вашим вызывающим контекстом, вам нужно использовать другой, зависящий от контекста механизм, такой как Dispatcher.Invoke() или что угодно в вашей среде.

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