How to execute a dynamic list of async functions in a sequential way? - asynchronous

I have refactored a Web API to rely on async/await in ASP.NET Core 3.1 and I have the following scenario: a statistics method is sequentially computing a list of indicators which are defined in a list.
readonly Dictionary<StatisticItemEnum, Func<Task<SimpleStatisticItemApiModel>>> simpleItemActionMap =
new Dictionary<StatisticItemEnum, Func<Task<SimpleStatisticItemApiModel>>>();
private void InitSimpleStatisticFunctionsMap()
{
simpleItemActionMap.Add(StatisticItemEnum.AllQuestionCount, GetAllQuestionCountApiModel);
simpleItemActionMap.Add(StatisticItemEnum.AllAnswerCount, GetAllAnswerCountApiModel);
simpleItemActionMap.Add(StatisticItemEnum.AverageAnswer, GetAverageAnswer);
// other mappings here
}
private async Task<SimpleStatisticItemApiModel> GetAllQuestionCountApiModel()
{
// await for database operation
}
private async Task<SimpleStatisticItemApiModel> GetAllAnswerCountApiModel()
{
// await for database operation
}
private async Task<SimpleStatisticItemApiModel> GetAverageAnswer()
{
// await for database operation
}
The code sequentially goes through each item and computes it and after the refactoring it is looking like this:
itemIds.ForEach(itemId =>
{
var itemEnumValue = (StatisticItemEnum) itemId;
if (simpleItemActionMap.ContainsKey(itemEnumValue))
{
var result = simpleItemActionMap[itemEnumValue]().Result;
payload.SimpleStatisticItemModels.Add(result);
}
});
I know that Task.Result might lead to deadlocks, but I could not find any other way to make this work.
Question: How to execute a dynamic list of async functions in a sequential way?

You should change the ForEach call to a regular foreach, and then you can use await:
foreach (var itemId in itemIds)
{
var itemEnumValue = (StatisticItemEnum) itemId;
if (simpleItemActionMap.ContainsKey(itemEnumValue))
{
var result = await simpleItemActionMap[itemEnumValue]();
payload.SimpleStatisticItemModels.Add(result);
}
}
Do not make the ForEach lambda async; that will result in an async void method, and you should avoid async void.

I think you can do this:
itemIds.ForEach(async itemId =>
{
var itemEnumValue = (StatisticItemEnum) itemId;
if (simpleItemActionMap.ContainsKey(itemEnumValue))
{
var result = await simpleItemActionMap[itemEnumValue]();
payload.SimpleStatisticItemModels.Add(result);
}
});

Related

How To Using Dataflow With Only BroadcastBlock And ActionBlock

This is my first question in SO, i'm new using DataFlow with BroadcastBlock and ActionBlock, i hope i can get solution in here.
Here's the structure.
Model
class SampleModel
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public bool Success { get; set; } = true;
public object UniqueData { get; set; }
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine($"Id - {Id}");
sb.AppendLine($"Success - {Success}");
sb.AppendLine($"UniqueData - {UniqueData}");
string tmp = sb.ToString();
sb.Clear();
return tmp;
}
}
DataFlow Logic
class CreateDownloadTask
{
public async Task VeryLongProcess()
{
await Task.Run(async () =>
{
Console.WriteLine("Long Process Working..");
await Task.Delay(TimeSpan.FromSeconds(5));
Console.WriteLine("Long Process Done..");
});
}
public async Task CreateSimpleBroadcastX<T>(T data)
{
Action<T> process = async model =>
{
Console.WriteLine("Working..");
await VeryLongProcess();
Console.WriteLine("Done");
};
var broad = new BroadcastBlock<T>(null);
var action = new ActionBlock<T>(process);
var dflo = new DataflowLinkOptions { PropagateCompletion = true };
broad.LinkTo(action, dflo);
await broad.SendAsync(data);
broad.Complete();
await action.Completion.ContinueWith(async tsk =>
{
Console.WriteLine("Continue data");
}).ConfigureAwait(false);
Console.WriteLine("All Done");
}
}
Caller
var task = cdt.CreateSimpleBroadcastX<SampleModel>(new SampleModel
{
UniqueData = cdt.GetHashCode()
});
task.GetAwaiter().GetResult();
Console.WriteLin("Completed");
I expect the result should be
Working..
Long Process Working..
Long Process Done..
Done
Continue data
All Done
Completed
But what i've got is
Working..
Long Process Working..
Continue data
All Done
Completed
Long Process Done..
Done
This is happen when ther is async-await inside of ActionBlock.
Now, the question is, is that possible to make the result as i expected without WaitHandle ?
That mean, ActionBlock.Completion will be wait until the Action or Delegate inside the ActionBlock is complete executed?
Or i'm i doing it wrong with that patter?
Thanks in Advance, and sorry for my bad english.
Your problem is here:
Action<T> process = async model => ...
That code creates an async void method, which should be avoided. One of the reasons you should avoid async void is because it is difficult to know when the method has completed. And this is exactly what is happening: the ActionBlock<T> cannot know when your delegate has completed because it is async void.
The proper delegate type for an asynchronous method without a return value that takes a single argument is Func<T, Task>:
Func<T, Task> process = async model => ...
Now that the asynchronous method returns a Task, the ActionBlock can know when it completes.

How to use putIfAbsent for when action returns Future

In my class I'm loading some files, and for efficiency I wanted to make a thread safe cache. I see in the map class that there is a putIfAbsent method, but it doesn't accept async types. Also not sure if this structure in general is safe to use.
This is the style of what I'm trying to do:
final Map<String, String> _cache = new Map();
Future<String> parse(final String name) async {
_cache.putIfAbsent(name, () async { // this async is not allowed
return await new File(name).readAsString();
});
return _cache[name];
}
Since I can use async on the parameter I've opted to use locks instead, but it makes the code far more verbose..
final Lock _lock = new Lock();
final Map<String, String> _cache = new Map();
Future<String> parse(final String name) async {
if (!_cache.containsKey(name)) {
await _lock.synchronized(() async {
if (!_cache.containsKey(name)) {
_cache[name] = await new File(name).readAsString();
}
});
}
return _cache[name];
}
Does anyone know how I can simplify this code, or if there are better libraries I can use for thread safe cache?
What do you mean by "this async is not allowed"? I see no particular issue with the putIfAbsent code, and I believe it should work.
The one probelem I see is that the cache is not caching futures, but strings. Since your function is returning a future anyway, you might as well store the future in the cache.
I would write it as:
final Map<String, Future<String>> _cache = new Map();
Future<String> parse(final String name) =>
_cache.putIfAbsent(name, () => File(name).readAsString());
but apart from fixing the _cache map type, that is effectively the same, it's just avoiding creating and waiting for a couple of extra futures.
I've created an extension to support an asynchronous action for putIfAbsent:
extension MapUtils<K, V> on Map<K, V> {
Future<V> putIfAbsentAsync(K key, FutureOr<V> Function() action) async {
final V? previous = this[key];
final V current;
if (previous == null) {
current = await action();
this[key] = current;
} else {
current = previous;
}
return current;
}
}
You can use like this:
final Map<String, String> _cache = {};
Future<String> parse(final String name) async {
return await _cache.putIfAbsentAsync(
name,
() async => await File(name).readAsString(),
// ^^^^^ this `async` is now allowed
);
}

Cosmodb, why is this update not working?

I am trying to query orders and update them. I have been able to isolate my problem in a unit test:
[Fact(DisplayName = "OrderDocumentRepositoryFixture.Can_UpdateAsync")]
public async void Can_UpdateByQueryableAsync()
{
var order1 = JsonConvert.DeserializeObject<Order>(Order_V20170405_133926_9934934.JSON);
var orderId1 = "Test_1";
order1.Id = orderId1;
await sut.CreateAsync(order1);
foreach (var order in sut.CreateQuery())
{
order.Version = "myversion";
await sut.UpdateAsync(order);
var ordersFromDb = sut.GetByIdAsync(orderId1).Result;
Assert.Equal("myversion", ordersFromDb.Version);
}
}
where :
public IQueryable<T> CreateQuery()
{
return _client.CreateDocumentQuery<T>(UriFactory.CreateDocumentCollectionUri(_databaseId, CollectionId));
}
With this code, orders are not updated.
If I replace the CreateQuery() by what follows, it does work:
[Fact(DisplayName = "OrderDocumentRepositoryFixture.Can_UpdateAsync")]
public async void Can_UpdateByQueryableAsync()
{
var order1 = JsonConvert.DeserializeObject<Order>(Order_V20170405_133926_9934934.JSON);
var orderId1 = "Test_1";
order1.Id = orderId1;
await sut.CreateAsync(order1);
var order = sut.GetByIdAsync(orderId1).Result;
order.Version = "myversion";
await sut.UpdateAsync(order);
var ordersFromDb = sut.GetByIdAsync(orderId1).Result;
Assert.Equal("myversion", ordersFromDb.Version);
}
where
public async Task<T> GetByIdAsync(string id)
{
try
{
var documentUri = UriFactory.CreateDocumentUri(_databaseId, CollectionId, id);
var document = (T) await ((DocumentClient) _client).ReadDocumentAsync<T>(documentUri);
return document;
}
catch (DocumentClientException e)
{
if (e.StatusCode == HttpStatusCode.NotFound) return null;
throw;
}
}
I've been trying to understand why this doesn't work. Obviously i could always do a GetByIdAsync before updating, but that seems overkill?
What can't I see?
Thanks!
You create your query, but you never execute it (CreateDocumentQuery just sets up the query). Try altering your call to something like:
foreach (var order in sut.CreateQuery().ToList())
{
//
}
Also note: if you are always querying for a single document, and you know the id, then ReadDocumentAsync() (your alternate code path) will be much more effecient, RU-wise.

async method cannot return void - How can I handle this?

I have a phone application. When a screen displays I start a timer like this:
base.OnAppearing();
{
timerRunning = true;
Device.BeginInvokeOnMainThread(() => showGridTime(5));
}
async void showGridTime(int time)
{
while (timerRunning)
{
var tokenSource = new CancellationTokenSource();
await Task.Delay(time, tokenSource.Token);
detailGrid.IsVisible = true;
}
}
The code seems to work but there is a warning message in the IDE saying that an async method cannot return null.
Given this code can someone help and give me advice on what I should return and if I am going about this in the correct way?
Just return a task:
async Task ShowGridTimeAsync(int time)
{
while (timerRunning)
{
var tokenSource = new CancellationTokenSource();
await Task.Delay(time, tokenSource.Token);
detailGrid.IsVisible = true;
}
}
This is necessary to have the caller of this method know when it is completed and act accordingly.
It is not recommended to create async void methods unless you're creating an event or forced to do that in order to meet an interface signature.

Making a class Async for WinRT / Windows 8 Developement

I'm developing a Win 8 app which needs to do some possibly long running looping and calculations based around the data input by the user. This calculation is run often to update the results in real time.
The calculations are done by a calculator class. I will use example code to give an idea
public class ResultCalculator
{
List<Data> Input1 = new List<Data>();
IQueryable<int> Input2;
IQueryable<Data2> Input3;
public ResultCalculator(List<int> items1, List<Data2> items2)
{
items1.Sort((x,y) => y.CompareTo(x));
Input2 = items1.AsQueryable();
Input3 = items2.AsQueryable().OrderByDescending(w => w.LValue);
}
public void CalculateLValues()
{
foreach (var v in Input3)
{
for (int i = 1; i <= v.Quantity; i++)
{
if (Input1.Count > 0)
{
Data existing = FindExisting(v.LValue, 4);
if (existing != null)
{
existing.Add(v.LValue);
}
else
{
FindNew(v.LValue);
}
}
else
{
FindNew(v.LValue);
}
}
}
OptimisePass1();
}
public void FindNew(int LValue)
{
int options = FindNewItem(LValue, 0);
if (options != 0)
{
Data newdata = new Data(options);
newdata.Add(LValue);
Input1.Add(newdata);
}
}
public void OptimisePass1()
{
foreach (var w in Input1)
{
var ShorterLValues = from sl in Input2 where sl < w.LValue orderby sl select sl;
foreach (var sl in ShorterLValues)
{
if (sl > w.LValue - w.Remaining)
{
w.LValue = sl;
w.CalculateRemaining();
}
}
// MORE CALCULATION TO DO IN ANOTHER LOOP
}
}
public Data FindExisting(int LValueRequired, int additionalvalue)
{
Input1.OrderBy(w => w.LValue);
foreach (var sl in Input1.Where(i => i.Remaining > 0).OrderBy(i => i.Remaining))
{
if (sl.Remaining >= LValueRequired + additionalvalue)
{
return sl;
}
}
return null;
}
public int FindNewItem(int LValueRequired)
{
foreach (var sl in Input2)
{
if (sl >= LValueRequired + 4)
{
return sl;
}
}
return 0;
}
}
This class is the used from my ViewModel as below...
public async Task UpdateCalculationAsync()
{
var data1 = new List<int>(sInput);
var reqlist = new List<Data2>(sInput2);
var lc = new ResultCalculator(data1, data2);
NL.Clear();
await Task.Run(() => lc.CalculateLValues());
foreach (var i in lc.Data1)
{
NL.Add(i);
}
}
Without any async use this held up the UI when it ran if there were many items in the lists. So I added the "await Task.Run(() => lc.CalculateLValues())" to make it run async. I've got a basic grasp of async but not really understood how to properly make my classes run in async. Is this approach correct?
I believe that what I've done hands off the calculation to the background thread. Certainly the UI now remains responsive and can be used whilst the calculation is running. Once the result is calculated my viewmodel gets the result and the UI updates. What I'd really rather have is for my ResultCalculator class to have Task returning methods which I can await. However I'm really struggling on how to refactor for that. I'm not even sure there's a need if this works and is a valid approach. But I'm not 100% convinced it is the proper use of the async pattern and wanted to check if it can be improved?
suggest to do something like this:
// user will do some action on the UI to trigger the compute
public async Task Button1_Click(..)
{
await this.ViewModel.RecalculateAsync();
}
class ViewModel
{
public async Task RecalculateAsync()
{
var data1 = new List<int>(sInput);
var reqlist = new List<Data2>(sInput2);
var lc = new ResultCalculator(data1, data2);
await Task.Run(() => lc.CalculateLValues());
// I am not sure that is NL - if it is items like property on viewmodel that is bound to ListView like control in xaml - that should be fine.
// otherwise, add code here to add the list of result items to an observable collection
// expose this collection as Items property on viewmodel and bind in xaml to ListView.ItemsSource
}
}

Resources