.net 코어 AsyncLocal의 가치 상실
HttpContextAccessor 와 유사한 패턴을 사용합니다.
단순화 된 버전은 다음과 같으며 Console.WriteLine(SimpleStringHolder.StringValue)
null이 아니어야합니다.
public class SimpleStringHolder
{
private static readonly AsyncLocal<ValueHolder> CurrentHolder = new AsyncLocal<ValueHolder>();
public static string StringValue
{
get => CurrentHolder.Value?.StringValue;
set
{
var holder = CurrentHolder.Value;
if (holder != null)
{
holder.StringValue = null;
}
if (value != null)
{
CurrentHolder.Value = new ValueHolder() { StringValue = value };
}
}
}
private class ValueHolder
{
public string StringValue;
}
}
class Program
{
private static readonly AsyncLocal<string> currentValue = new AsyncLocal<string>();
public static void Main(string[] args)
{
var task = Task.Run(async () => await OutterAsync());
task.Wait();
}
public static async Task OutterAsync()
{
SimpleStringHolder.StringValue = "1";
await InnerAsync();
Console.WriteLine(SimpleStringHolder.StringValue); //##### the value is gone ######
}
public static async Task InnerAsync()
{
var lastValue = SimpleStringHolder.StringValue;
await Task.Delay(1).ConfigureAwait(false);
SimpleStringHolder.StringValue = lastValue; // comment this line will make it work
Console.WriteLine(SimpleStringHolder.StringValue); //the value is still here
}
}
위의 코드에서, OutterAsync
비동기 메소드를 호출 InnerAsync
에서 InnerAsync
가 StringValue
AsyncLocal는 그것의 문맥을 상실하게되는 설정 OutterAsync
Console.WriteLine(SimpleStringHolder.StringValue);
널.
마법은 SimpleStringHolder의 속성 집합에 있다고 생각하며 다음 코드를 제거하면 문제가 해결됩니다.
if (holder != null)
{
holder.StringValue = null;
}
위의 코드는 예상대로 작동합니다.
이게 무슨 마법인지 이해하도록 도와주세요.
답변
AsyncLocal<T>
비동기 실행 컨텍스트 내에서 값을 보존하는 메커니즘을 제공하기 위해 존재합니다. 이에 대한 핵심은 귀하의 예와 관련된 두 가지 요소입니다.
- 를
await
사용하면 메서드가 호출자에게 반환되어 컨텍스트를 변경할 수 있습니다. 이전ThreadLocal<T>
유형을 사용하면 실행이 메서드에 제어를 반환 할 때async
컨텍스트가 동일 하더라도 다른 스레드에있을 수 있습니다 . 를 사용AsyncLocal<T>
하면await
대기 가능한 개체가 완료된 후 메서드에 컨트롤 이 반환 될 때 컨텍스트의 상태가 복원됩니다 . - 컨텍스트를 변경해야하는 상황이 발생할 때까지
AsyncLocal<T>
개체 의 현재 상태 는 이전 상태가됩니다. 즉, 메서드는 기본적으로 개체가 호출되었을 때의 상태를 상속합니다. 간단한 값을 처리하는 경우 놀라움이 숨어 있지 않지만 유형과 같은 참조 유형의 경우 추적ValueHolder
하는 유일한 것은 해당 객체에AsyncLocal<T>
대한 참조 입니다. 객체의 복사본은 여전히 하나 뿐이며, 주어진 객체의 상태 변경은 항상 비동기 컨텍스트가 떠 다니는 것과 함께 또는없는 것처럼 작동합니다 (즉, 해당 객체에 대한 참조로 표시됨).
따라서 코드 예제에서 다음을 제공했습니다.
OutterAsync()
StringValue
속성을로 설정하면"1"
새ValueHolder
객체가 생성StringValue
되고 해당 객체 의 속성이로 설정됩니다"1"
.OutterAsync()
전화InnerAsync()
. 그런 다음 해당 메서드string
는 소유자로부터 참조 를 검색합니다 (간접적으로 ... 즉SimpleStringHolder.StringValue
속성을 통해 이동 ). 이 시점에서 값이나 컨텍스트가 변경되지 않았으므로이 경우 동일한ValueHolder
객체가 사용되므로"1"
다시 돌아옵니다.InnerAsync()
비동기 작업을 기다립니다AsyncValue<T>
.이 경우 해당 컨텍스트 에 대한 개체 변경 사항을 격리하기 위해 새 실행 컨텍스트가 생성 됩니다. 이 시점부터 객체의 변경 사항 은 다른 컨텍스트의 코드에서 볼 수 없습니다 . 예를 들어OutterAsync()
메서드 에서 실행되는 코드 입니다.- 에서 비동기 작업이 완료된 후
InnerAsync()
해당 메서드는SimpleStringHolder.StringValue
속성에 새 값을 설정 합니다. 이전 콘텍스트가 상속 때문에 세터 세트 때holder.StringValue
하는null
그것이에서 생성 된 객체의 속성을 설정한다OutterAsync()
. 그러나 ... 코드가 새 컨텍스트에 있기 때문에 setter가 새 값을CurrentHolder.Value
속성에 할당하면 해당 변경 내용이 해당 컨텍스트에 격리됩니다. - 때
InnerAsync()
방법이 마침내 완료의이 완료 작업하는 것이OutterAsync()
방법의은await
에 기다리고 있었다. 이로 인해 는 값을 업데이트 할 때 있었던 컨텍스트와 다른 메서드의 컨텍스트로AsyncValue<T>
상태를 복원 합니다. 특히이 복원 된 상태는 속성이 null로 설정 되었을 때 원래 설정된 객체에 대한 참조 입니다.OutterAsync()
InnerAsync()
SimpleStringHolder.StringValue
ValueHolder
SimpleStringHolder
holder.StringValue
- 따라서
OutterAsync()
속성 값을 살펴보면 null로 설정된 것을 찾습니다. null로 설정 되었기 때문입니다.
자신의 실험에서 null 할당을 완전히 제거하거나 의 문 SimpleStringHolder.StringValue
뒤에 할당을 생략 할 수 있습니다 (할당을 하지 않으면 null 할당이 실행되지 않기 때문입니다). 어느 쪽이든 null 할당이 발생하지 않으므로 이전에 할당 된 값이 유지됩니다.InnerAsync()
await
그러나 null 할당을 수행하면 호출자 OutterAsync()
가 컨텍스트를 복원하고 홀더 개체 참조를 복원 한 다음 해당 홀더 개체의 자체 string
참조가 이미로 설정되어 null
있으므로 이것이 표시 OutterAsync()
됩니다.
관련 읽기 :
비 비동기 / 대기 코드에서 AsyncLocal의 효과는 무엇입니까?
코드가 약간 리팩터링 될 때 AsyncLocal이 다른 결과를 반환하는 이유는 무엇입니까?
않습니다 AsyncLocal도 일하지 ThreadLocal않습니다를?