Unable to await SetStringAsync in IDistributedCache - c#

I am using .NET Core 2.0 with Microsoft.Extensions.Caching.Redis 2.0. Why is IDistributedCache.SetStringAsync not actually marked as async, and therefore not awaitable?
var services = new ServiceCollection();
services.AddDistributedRedisCache(options =>
{
options.Configuration = "127.0.0.1";
options.InstanceName = "master";
});
var provider = services.BuildServiceProvider();
var cache = provider.GetService<IDistributedCache>();
// Does not compile
await cache.SetStringAsync("mykey", "myvalue");

Those methods are defined to return Task (they are extension methods in DistributedCacheExtensions):
public static Task SetStringAsync(this IDistributedCache cache, string key, string value)
{
return cache.SetStringAsync(key, value, new DistributedCacheEntryOptions());
}
And you can use await with such kind of method as (MSDN)
The task to which the await operator is applied typically is returned by a call to a method that implements the Task-Based Asynchronous Pattern. They include methods that return Task, Task<TResult>, and System.Threading.Tasks.ValueType<TResult> objects.
but you cannot use await in the method that is not marked as async, that's why you have a compilation error:
await can only be used in an asynchronous method modified by the async keyword.
If you are interested in what would have been the difference if the team had marked SetStringAsync as async method, like:
public static async Task SetStringAsync
then look into SO "async Task then await Task" vs "Task then return task"

Related

Run an async method only once, and return the same result to all concurrent and future calls [duplicate]

This question already has answers here:
Enforce an async method to be called once
(4 answers)
Closed 6 months ago.
I'm writing an ASP.net Core 6 application (but the question is more about C# in general) where I have a controller action like this:
[HttpGet]
public async Task<IActionResult> MyAction() {
var result = await myService.LongOperationAsync();
return Ok(result);
}
Basically, the action calls a service that needs to do some complex operation and can take a bit of time to respond, up to a minute. Obviously, if in the meantime another request arrives a second run of LongOperationAsync() starts, consuming even more resources.
What I would like to do is redesign this so that the calls to LongOperationAsync() don't run in parallel, but instead wait for the result of the first call and then all return the same result, so that LongOperationAsync() only runs once.
I thought of a few ways to implement this (for example by using a scheduler like Quartz to run the call and then check if a relevant Job is already running before enqueueing another one) but they all require quite a bit of relatively complicated plumbing.
So I guess my questions are:
Is there an established design pattern / best practice to implement this scenario? Is it even practical / a good idea?
Are there features in the C# language and/or the ASP.net Core framework that facilitate implementing something like this?
Clarification: basically I want to run the long-running operation only once, and "recycle" the result to any other call that was waiting without executing the long-running operation again.
You could use an async version of Lazy<T> to do this.
Stephen Toub has posted a sample implementation of LazyAsync<T> here, which I reproduce below:
public class AsyncLazy<T> : Lazy<Task<T>>
{
public AsyncLazy(Func<T> valueFactory) :
base(() => Task.Run(valueFactory))
{ }
public AsyncLazy(Func<Task<T>> taskFactory) :
base(() => Task.Run(taskFactory))
{ }
public TaskAwaiter<T> GetAwaiter() { return Value.GetAwaiter(); }
}
You could use it like this:
public class Program
{
public static async Task Main()
{
var test = new Test();
var task1 = Task.Run(async () => await test.AsyncString());
var task2 = Task.Run(async () => await test.AsyncString());
var task3 = Task.Run(async () => await test.AsyncString());
var results = await Task.WhenAll(task1, task2, task3);
Console.WriteLine(string.Join(", ", results));
}
}
public sealed class Test
{
public async Task<string> AsyncString()
{
Console.WriteLine("Started awaiting lazy string.");
var result = await _lazyString;
Console.WriteLine("Finished awaiting lazy string.");
return result;
}
static async Task<string> longRunningOperation()
{
Console.WriteLine("longRunningOperation() started.");
await Task.Delay(4000);
Console.WriteLine("longRunningOperation() finished.");
return "finished";
}
readonly AsyncLazy<string> _lazyString = new (longRunningOperation);
}
If you run this console app, you'll see that longRunningOperation() is only called once, and when it's finished all the tasks waiting on it will complete.
Try it on DotNetFiddle
As Matthew's answer points out, what you're looking for is an "async lazy". There is no built-in type for this, but it's not that hard to create.
What you should be aware of, though, is that there are a few design tradeoffs in an async lazy type:
What context the factory function is run on (the first invoker's context or no context at all). In ASP.NET Core, there isn't a context. So the Task.Factory.StartNew in Stephen Toub's example code is unnecessary overhead.
Whether failures should be cached. In the simple AsyncLazy<T> approach, if the factory function fails, then a faulted task is cached indefinitely.
When to reset. Again, by default the simple AsyncLazy<T> code never resets; a successful response is also cached indefinitely.
I'm assuming you do want the code to run multiple times; you just want it not to run multiple times concurrently. In that case, you want the async lazy to be reset immediately upon completion, whether successful or failed.
The resetting can be tricky. You want to reset only when it's completed, and only once (i.e., you don't want your reset code to clear the next operation). My go-to for this kind of logic is a unique identifier; I like to use new object() for this.
So, I would start with the Lazy<Task<T>> idea, but wrap it instead of derive, which allows you to do a reset, as such:
public class AsyncLazy<T>
{
private readonly Func<Task<T>> _factory;
private readonly object _mutex = new();
private Lazy<Task<T>> _lazy;
private object _id;
public AsyncLazy(Func<Task<T>> factory)
{
_factory = factory;
_lazy = new(_factory);
_id = new();
}
private (object LocalId, Task<T> Task) Start()
{
lock (_mutex)
{
return (_id, _lazy.Value);
}
}
private void Reset(object localId)
{
lock (_mutex)
{
if (localId != _id)
return;
_lazy = new(_factory);
_id = new();
}
}
public async Task<T> InvokeAsync()
{
var (localId, task) = Start();
try
{
return await task;
}
finally
{
Reset(localId);
}
}
}

Should async or non-async methods be used in SignalR Hub?

I have the following Hub class in SignalR part and here I defined all of the methods related to connection:
public override Task OnConnected()
{
// here I cannot call this, and need to convert this method async
await AddToGroup("stockGroup");
//
string name = Context.User.Identity.Name;
_connections.Add(name, Context.ConnectionId);
return base.OnConnected();
}
public async Task AddToGroup(string groupName)
{
await Groups.Add(Context.ConnectionId, groupName);
await Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId} joined");
}
I have a look at many different examples regarding to these connection methods (and other Hub methods) and see that some of them use async method while some of them not. In this example above, I need to convert OnConnected() method to async in order to call AddToGroup() method. Of course the reverse situation would also be ok, but I am not sure which one is better. So, should I use async methods or non-async methods for all of the methods in the Hub? Any help would be appreciated.
Update 1 : Converted method (to async).
public override async Task OnConnected()
{
await AddToGroup("stockGroup");
string name = Context.User.Identity.Name;
_connections.Add(name, Context.ConnectionId);
return base.OnConnected();
}
Update 2:
public override async Task OnConnected()
{
// #1 There is no async method in "Microsoft.AspNet.SignalR" library.
//await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
// #2 I just have sync version of "AddToGroupAsync()" and used it
await Groups.Add(Context.ConnectionId, "SignalR Users");
/* #3 I think there is no need to use this custom method in the Hub.
Because the same method is already exist in the IGroupManager interface */
//await AddToGroup("jiraGroup");
string name = Context.User.Identity.Name;
_connections.Add(name, Context.ConnectionId);
// #4 Here also the same problem and I used sync version of OnConnected()
//await base.OnConnectedAsync();
await base.OnConnected();
}
Just add async if you need await inside method. async is a marker for compiler to generate async state machine.
should I use async- use if you need to await.
If you don't need to await just return task itself without async. In that case you can avoid creation of state machine.
Also please read Async/Await FAQ from Stephen Toub:
When you mark a method with the “async” keyword, you’re really telling
the compiler two things:
You’re telling the compiler that you want to be able to use the
“await” keyword inside the method (you can use the await keyword if
and only if the method or lambda it’s in is marked as async). In doing
so, you’re telling the compiler to compile the method using a state
machine, such that the method will be able to suspend and then resume
asynchronously at await points.
You’re telling the compiler to “lift”
the result of the method or any exceptions that may occur into the
return type. For a method that returns Task or Task, this
means that any returned value or exception that goes unhandled within
the method is stored into the result task. For a method that returns
void, this means that any exceptions are propagated to the caller’s
context via whatever “SynchronizationContext” was current at the time
of the method’s initial invocation.
You should use Async methods with await and return Task.
Asynchrony is viral so you should avoid async void.
Bad:
public async void MethodAsync()
{
var result = await SendAsync();
DoSomething(result);
}
Good:
public async Task MethodAsync()
{
var result = await SendAsync();
DoSomething(result);
}
There are some awesome async guidelines by #davidfowl here.
Update: remove the return:
public override async Task OnConnected()
{
await AddToGroup("stockGroup");
string name = Context.User.Identity.Name;
_connections.Add(name, Context.ConnectionId);
await base.OnConnected();
}

Compose async methods with await? [duplicate]

This question already has an answer here:
eliding async and await in async methods [duplicate]
(1 answer)
Closed 4 years ago.
When passing through method calls to another async method, should the caller method also be async and use await, or should I simply pass through the Task it receives from the callee? What if the calling method performs a bit more preparation?
public Task<Message> Unsubscribe(int subscriptionId, CancellationToken cancellationToken)
{
var data = new MessageData
{
["subscriptionId"] = subscriptionId
};
return SendAsync(OpCode.Unsubscribe, data, cancellationToken);
}
public Task<Message> Unsubscribe(int subscriptionId) =>
Unsubscribe(subscriptionId, CancellationToken.None);
SendAsync is async and returns Task<Message>. So should the first overload of Unsubscribe be like above or like that:
public async Task<Message> Unsubscribe(int subscriptionId, CancellationToken cancellationToken)
{
var data = new MessageData
{
["subscriptionId"] = subscriptionId
};
return await SendAsync(OpCode.Unsubscribe, data, cancellationToken);
}
The other alternative is with the second overload of Unsubscribe. It might be like above or like that:
public async Task<Message> Unsubscribe(int subscriptionId) =>
await Unsubscribe(subscriptionId, CancellationToken.None);
I guess that more asyncs and awaits add complexity introduced by the compiler (I see it in the stack traces!) and may degrade performance and memory consumption. But at least it should provide for a consistent exception propagation.
In the examples you cited, just returning the task without awaiting it is fine (and arguably preferable), but this does require some care.
Once case were you can get into trouble is when you're dealing with Tasks inside a using block. These can have vastly different behaviors:
public async Task<Something> AwaitTheTask()
{
using (var someResource = GetAResource())
{
return await SomeAsyncThing(someResource);
}
}
public Task<Something> DontAwaitTheTask()
{
using (var someResource = GetAResource())
{
return SomeAsyncThing(someResource);
}
}
In the first example, the using block will not dispose someResource until the awaited Task has completed. In the second example, someResource will be disposed right away, very likely causing problems for the code that needs that resource.

Setting up a C# Test with Moq against Async methods

I'm trying to create a set of test methods using Moq to cover the external dependencies. These dependencies are async in nature and I have come across a set of them that when awaited they never return, so I'm not sure what I'm missing.
The test itself is very simple.
[TestMethod]
public async Task UpdateItemAsync()
{
var repository = GetRepository();
var result = await repository.UpdateItemAsync("", new object());
Assert.IsNotNull(result);
}
The GetRepository method above is what sets up the various mock objects including called Setup on them.
private static DocumentDbRepository<object> GetRepository()
{
var client = new Mock<IDocumentClient>();
var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
client.Setup(m => m.ReplaceDocumentAsync(It.IsAny<Uri>(), It.IsAny<object>(), It.IsAny<RequestOptions>()))
.Returns(() =>
{
return new Task<ResourceResponse<Document>>(() => new ResourceResponse<Document>());
});
var repository = new DocumentDbRepository<object>(configuration, client.Object);
return repository;
}
The code that is under test is listed below and when the line with the await is executed it never returns.
public async Task<T> UpdateItemAsync(string id, T item)
{
var result = await Client.ReplaceDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, CollectionId, id), item);
return result.Resource as T;
}
I'm sure that the error is in the Setup method on the Moq object in the GetRepository method, but I'm not sure what the problem is.
You need to fix the set up on the async calls
Moq has a ReturnsAsync that would allow the mocked async method calls to flow to completion.
client
.Setup(_ => _.ReplaceDocumentAsync(It.IsAny<Uri>(), It.IsAny<object>(), It.IsAny<RequestOptions>()))
.ReturnsAsync(new ResourceResponse<Document>());
You typically want to avoid newing up Tasks manually

How to synchronously call async method from quartz schedule job

I am trying to call a webapi method from my quartz.net schedule job. I am not sure whether the way I am doing is right? Can anyone help if this is the right way or is there any better approach available?
MethodRepository.cs
public async Task<IEnumerable<ResultClass>> GetResult(string queryCriteria)
{
return await _httpClient.Get(queryCriteria);
}
Quartz job:
public async void Execute(IJobExecutionContext context)
{
var results= await _repo.GetResult();
}
generic Httpclient :
public async Task<IEnumerable<T>> Get(string queryCriteria)
{
_addressSuffix = _addressSuffix + queryCriteria;
var responseMessage = await _httpClient.GetAsync(_addressSuffix);
responseMessage.EnsureSuccessStatusCode();
return await responseMessage.Content.ReadAsAsync<IEnumerable<T>>();
}
But the quartz documentation says I can't use async method in a quartz job. How can one the Web API method then?
Can I change the quartz job execute method as:
public void Execute(IJobExecutionContext context)
{
var result = _repo.GetResult().Result;
}
Quartz.NET 3.0 supports async/await out of the box. So you can (and must) now declare Execute method as Task returning and you can use async/await.
public async Task Execute(IJobExecutionContext context)
{
var result = await _repo.GetResult();
}
If you have to do it - then yes you can do that, but it will block the calling thread until the asynchronous operation is complete.
Task.Result will wrap any exception into an AggregateException.
So you should probably put your httpclient call in a try catch.
try
{
var result = _repo.GetResult().Result;
}
catch (AggregateException ae)
{
// handle exception
}
Also, it seems they are working on an AsyncJob.

Categories

Resources