Если вы попробуете этот код, вы увидите, что каждый новый асинхронный поток c создает новое значение. Поэтому ответ должен быть: Да, у вас должно быть уникальное значение для каждого запроса.
private static readonly AsyncLocal<object> Item = new AsyncLocal<object>();
public static async Task Main()
{
async Task Request()
{
if (Item.Value is {})
{
Console.WriteLine("This should never happen.");
throw new InvalidOperationException("Value should be null here.");
}
Item.Value = new object();
}
await Task.Run(Request); // Just to be sure that Item.Value is initialized once.
await Task.WhenAll(
Task.Run(Request),
Task.Run(Request),
Task.Run(Request),
Task.Run(Request),
Task.Run(Request));
Console.WriteLine("finished");
}
DEMO
Но я попробовал немного больше сложный пример, чтобы определить, где заканчивается асинхронный поток c. Код очень прост, но массовое использование Console.WriteLine
делает его немного запутанным.
public class Program
{
public static async Task Main()
{
await Task.Run(async () =>
{
Console.WriteLine("Async flow entered...");
// Init async value
if (Cache.Instance.Item.Value is {})
throw new InvalidOperationException("The async flow has just startet. A value should not be initialized.");
var newValue = new object();
Console.WriteLine($"Create: value = #{RuntimeHelpers.GetHashCode(newValue)}");
Cache.Instance.Item.Value = newValue;
await Foo();
Console.WriteLine("Async flow exitted.");
});
Console.WriteLine("Main finished.\n\n");
}
private static async Task Foo()
{
Console.WriteLine($"Foo: entered...");
await Bar();
Console.WriteLine($"Foo: getting value...");
var knownValue = Cache.Instance.Item.Value;
Console.WriteLine($"Foo: value = #{RuntimeHelpers.GetHashCode(knownValue)}");
Console.WriteLine($"Foo: exitted.");
}
private static async Task Bar()
{
Console.WriteLine($"Bar: entered...");
await Task.CompletedTask;
Console.WriteLine($"Bar: exitted.");
}
}
public sealed class Cache
{
public static Cache Instance = new Cache();
public AsyncLocal<object> Item { get; } = new AsyncLocal<object>(OnValueChanged);
private static void OnValueChanged(AsyncLocalValueChangedArgs<object> args)
{
Console.WriteLine($"OnValueChanged! Prev: #{RuntimeHelpers.GetHashCode(args.PreviousValue)} Current: #{RuntimeHelpers.GetHashCode(args.CurrentValue)}");
}
}
DEMO
Вывод этого кода:
Async flow entered...
Create: value = #6044116
OnValueChanged! Prev: #0 Current: #6044116
Foo: entered...
Bar: entered...
Bar: exitted.
Foo: getting value...
Foo: value = #6044116
Foo: exitted.
Async flow exitted.
OnValueChanged! Prev: #6044116 Current: #0
Main finished.
Ожидается поток значений. Это отвечает на вопрос , присваивается ли значение значению по умолчанию - да, это так. Поток asyn c заканчивается там, где заканчивается Task.Run
, и в этот момент значение получает значение default
.
Но все становится интереснее, если вы измените await Task.CompletedTask;
в Bar
на await Task.Delay(1);
, Вывод выглядит очень по-разному:
Async flow entered...
Create: value = #6044116
OnValueChanged! Prev: #0 Current: #6044116
Foo: entered...
Bar: entered...
OnValueChanged! Prev: #6044116 Current: #0
OnValueChanged! Prev: #0 Current: #6044116
Bar: exitted.
Foo: getting value...
Foo: value = #6044116
Foo: exitted.
Async flow exitted.
OnValueChanged! Prev: #6044116 Current: #0
Main finished.
OnValueChanged! Prev: #0 Current: #6044116
OnValueChanged! Prev: #6044116 Current: #0
Странные детали начинаются после ввода Bar
. Похоже, что await Task.Delay(1)
нарушает асинхронный поток c. Но стоимость восстановлена верно. Здесь я могу, по крайней мере, придумать объяснение, угадав.
И по-настоящему интересная вещь происходит после того, как main закончен. Значение восстанавливается и очищается еще раз ... Мне не хватает воображения здесь. Я совершенно не представляю, почему и как восстанавливается значение после завершения Task.Run
, а также должен завершаться асинхронный поток c. Это дает мне ощущение, что G C не может очистить объект, пока программа не закончилась.