Closed ivaylokenov closed 4 years ago
cc: @sergiy-k
Did you get chance to debug it and find out what is wrong?
I have a similar problem. When I switch to ThreadLocal it works, but AsyncLocal does not.
A very simple code to re-produce it:
public class AsyncLocal_Tests
{
private static readonly AsyncLocal<string> _asyncLocal = new AsyncLocal<string>();
[Fact]
public async Task Test1()
{
await AsyncTestCode("42");
_asyncLocal.Value.ShouldBe("42"); //TEST FAILS IN THIS POINT... IT'S NULL!
}
private static async Task AsyncTestCode(string value)
{
using (var ms = new MemoryStream())
{
await ms.WriteAsync(new[] { (byte)1 }, 0, 1);
_asyncLocal.Value = value;
_asyncLocal.Value.ShouldBe(value);
await ms.WriteAsync(new[] { (byte)2 }, 0, 1);
}
}
}
This is the expected behavior. Any changes made to _asyncLocal.Value inside of an async
method do not propagate back out to the synchronous caller of the method. There is explicit code in the infrastructure for async
methods to prevent that from happening.
Thanks @stephentoub for the explanation.
But then how I can achive the scenario above? I was using CallContext.LogicalSetData
before, but it's not available in .netcore/.netstandard.
Did you consider simply returning value from the async method? (or use out/ref parameters if you have more values)
This is a very very simplified example to demonstrate the problem. My actual application/framework is much more complicated. What I want to have a ambient value shared in current thread/async-flow. In a web application, we can store such a value in HttpContext.Items. But if we have a background job or console app, this is also not possible. So, what I was looking for a good replacement for CallContext like in .net framework.
You could do something like this:
public static class AsyncLocal_Tests
{
private static readonly AsyncLocal<MyAsyncFlowState> _asyncLocal = new AsyncLocal<MyAsyncFlowState>();
private class MyAsyncFlowState
{
public string Str { get; set; }
}
[Fact]
public static async Task Test1()
{
_asyncLocal.Value = new MyAsyncFlowState();
await AsyncTestCode("42");
Console.WriteLine(_asyncLocal.Value.Str ?? "(null)");
}
private static async Task AsyncTestCode(string value)
{
using (var ms = new MemoryStream())
{
await ms.WriteAsync(new[] { (byte)1 }, 0, 1);
_asyncLocal.Value.Str = value;
Console.WriteLine(_asyncLocal.Value.Str ?? "(null)");
await ms.WriteAsync(new[] { (byte)2 }, 0, 1);
}
}
}
But this is not thread safe. Many thread can access to _asyncLocal.Value.Str concurrently and overwrite each other's values. Am I wrong?
_asyncLocal.Value
would be unique per ExecutionContext
, but in the async flow there could be multiple threads trying to access/change it. In your original code, changing _asyncLocal.Value
would change the current execution context, leaving the original execution context unchanged, which is why you don't see the updated value in the completion. Suppose it were instead to change the same execution context, you would then have the same thread safety issue. If you need thread safety, you may need to add it into MyAsyncFlowState
.
I will think on that, thanks a lot.
I think this there is still a problem. We may need to change ambient value from inner method and expect to effect the value in the containing method. .net framework should provide a way of that.
We may need to change ambient value from inner method and expect to effect the value in the containing method. .net framework should provide a way of that.
It's explicitly by design that you can't do that from inside of an async method, so it's not a bug. If you want to do that, you can't use an async method (it can still be Task-returning, just not using the async/await keywords).
OK, understand that this is by design. But, is there any other way of doing that (beside AsyncLocal)?
Every mechanism for flowing state across asynchronous points (e.g. AsyncLocal, CallContext, etc.) does so via ExecutionContext, and it's ExecutionContext that is prevented from flowing out of an async/await method. So any changes done via any such mechanism inside of an async/await method will not flow out. If you want changes to AsyncLocal, CallContext, etc. to flow out of an asynchronous method to its synchronous caller, that asynchronous method can't itself be marked as async... it could still be Task-returning, and it could make a change to something and then delegate to an async/await method, and the changes would propagate out to the synchronous caller fine.
Thank you for detailer explanation and for your valuable time. I tested and see that it's same for CallContext too. So, there is no way of what I want and should make it working with given rule.
@hikalkan, happy to help, thanks.
Steps to reproduce
I have the following class:
The following test runs successfully on DNX. Assume that TestServiceProvider always returns the same instanve of MockedMemoryCache and TestHelper just call Dispose, which clears the dictionary.
Expected behavior
The test to pass like it did on DNX.
Actual behavior
After moving to CLI the test started failing with unexpected values like
first
equalsthird
and so on. It seems that the internal dictionary is shared between the tasks and it should not be.Maybe I am missing something? Is this expected behavior? If you need more minimalistic example, I can provide one.
NOTE: After changing AsyncLocal to ThreadLocal the test passed right away.