I have a scenario where I need to make multiple api calls (same api with different parameters) parallely in c# (Xamarin iOS and Xamarin Android). And I don't want to wait for all tasks to complete, instead whenever a response comes I should process it and update the UI accordingly.
Method that needs to be called multiple times
public async Task<Response> GetProductsAsync(int categoryId, int pageNo = -1, int pageSize = -1)
{
try
{
string url = "";
if (pageNo == -1 || pageSize == -1)
url = $"catalog/v1/categories/{categoryId}/products";
else
url = $"catalog/v1/categories/{categoryId}/products?page-number={pageNo}&page-size={pageSize}";
var response = await client.GetAsync(url);
string responseString = await response.Content.ReadAsStringAsync();
GetParsedData(response.IsSuccessStatusCode, responseString);
}
catch (Exception e)
{
apiResponse.status = "internalError";
apiResponse.data = e.Message;
}
return apiResponse;
}
From calling function you may write the code as below
public void CallingFunctionToGetProductsAsync() {
Task.Run(async () =>
{
var response = await GetProductsAsync(1);
ProcessResponse(response);
});
Task.Run(async () =>
{
var response = await GetProductsAsync(2);
ProcessResponse(response);
});
}
This is how you can wait for multiple tasks asynchron and update the UI whenever any of them completes.
async Task GetSomeProductsAsync( IEnumerable<int> categoryIds )
{
List<Task<Response>> tasks = categoryIds
.Select( catId => GetProductsAsync( catId ) )
.ToList();
while ( tasks.Any() )
{
var completed = await Task.WhenAny( tasks );
tasks.Remove( completed );
var response = completed.Result;
// update the ui from this response
}
}
As a side note:
You should add ConfigureAwait(false) to your awaiting code in GetProducsAsync to avoid uneccessary sync with the caller thread (wich would be the UI here)
public async Task<Response> GetProductsAsync(int categoryId, int pageNo = -1, int pageSize = -1)
{
try
{
string url = "";
if (pageNo == -1 || pageSize == -1)
url = $"catalog/v1/categories/{categoryId}/products";
else
url = $"catalog/v1/categories/{categoryId}/products?page-number={pageNo}&page-size={pageSize}";
var response = await client.GetAsync(url).ConfigureAwait(false);
string responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
GetParsedData(response.IsSuccessStatusCode, responseString);
}
catch (Exception e)
{
apiResponse.status = "internalError";
apiResponse.data = e.Message;
}
return apiResponse;
}
You can read more about it in Stephen Cleary's blog article: Don't block on Async Code
Related
This question is kind of an extension to this one, I have updated my code below to reflect a task that runs continuously updating web clients using through a Websocket connection, the tasks checks for stock prices for a provided company (symbol) received in the Recv() method, when the user changes the company the Recv() method should cancel the running task (for the older company) and start a new one for the newly provided company/symbol.
The problem I'm having is that the line where await Task.Run(() => StockPricingAsync(jsonResult), priceToken.Token); in the Recv method gets executed doesn't cancel the existing task although there is a priceToken.Cancel just right before. Any idea how to get the running task cancelled and start the same one immediately?
I have the priceToken variable declared globally so it can be accessed from within the running task, but this does not work.
I also tried to assign the task to variable and make that variable = null but that did not cancel the task as well.
I'm also open to change the approach completely if I have to, all I need is a running task that provides pricing information for a passed parameter which gets changed by the client while the connection is still open.
CancellationTokenSource priceToken = new CancellationTokenSource();
public async Task GetDataAsync(WebSocket ws)
{
CancellationToken token = new();
try
{
var sendTask = Task.Run(() => Send(ws, token));
var recvTask = Task.Run(() => Recv(ws, token));
do
{
await Task.Delay(1000);
} while (ws.State == WebSocketState.Open);
}
finally
{
await ws.CloseAsync(WebSocketCloseStatus.Empty, "", token);
}
}
async Task Recv(WebSocket ws, CancellationToken token)
{
Console.WriteLine("Recv task started...");
var buffer = WebSocket.CreateClientBuffer(1024, 1024);
WebSocketReceiveResult taskResult;
while (ws.State == WebSocketState.Open)
{
string jsonResult = "";
do
{
taskResult = await ws.ReceiveAsync(buffer, token);
jsonResult += Encoding.UTF8.GetString(buffer.Array, 0, taskResult.Count);
} while (!taskResult.EndOfMessage);
if (!string.IsNullOrEmpty(jsonResult))
{
Console.WriteLine("Queueing {0}", jsonResult);
priceToken.Cancel()
priceToken = new CancellationTokenSource();
await Task.Run(() => StockPricingAsync(jsonResult), priceToken.Token);
}
}
Console.WriteLine("Recv task exiting...");
}
async static Task Send(WebSocket ws, CancellationToken token)
{
Console.WriteLine("Send task started...");
do
{
string sendMsg = sendQueue.Take();
Console.WriteLine("Sending {0}", sendMsg);
var sendMsgBytes = Encoding.UTF8.GetBytes(sendMsg);
ArraySegment<byte> segmentBuffer = new ArraySegment<byte>(sendMsgBytes, 0, sendMsgBytes.Length);
await ws.SendAsync(segmentBuffer, WebSocketMessageType.Text, true, token);
} while (ws.State == WebSocketState.Open);
Console.WriteLine("Send task exiting...");
}
public async Task StockPricingAsync(string symbol)
{
var lastUpdated = DateTime.MinValue;
double previousPrice = 0;
new StockService().GetStockPricing(symbol, true);
while (!priceToken.Token.IsCancellationRequested)
{
var price = new StockService().GetStockPricing(symbol, false);
if (price != null && lastUpdated != price.LastPriceDate)
{
lastUpdated = price.LastPriceDate;
if (price.LastPrice > previousPrice)
price.Tick = Stock.Tick.UpTick;
else if (price.LastPrice < previousPrice)
price.Tick = Stock.Tick.DownTick;
else
price.Tick = Stock.Tick.NoChange;
previousPrice = price.LastPrice;
var json = JsonConvert.SerializeObject(new ServerData(new KeyValuePair<string, object>("StockPricing", price), _eventMessage), _jsonSerializerSettings);
sendQueue.Add(json);
}
await Task.Delay(3000, this.priceToken.Token);
}
if (this.priceToken.Token.IsCancellationRequested)
this.priceToken.Token.ThrowIfCancellationRequested();
}
My code returns Task<List<...>>.
There is a type conversion error from Task<List<...>> to List<...>.
Tell me, please, where I did not finish await ?
public async Task<List<DepartamentsResponse>> Handle(GetDepartmentsRequest token, CancellationToken cancellationToken)
{
var departments = await _departmentApiClient.GetReception(token.accessToken, OdpgDepartmentType.Reception);
var result = departments.ConvertAll(async d => new DepartamentsResponse
{
FederalDistrict = GetFederalDistrictCode(d.FederalRegion.DistrictCode),
SubjectName = d.Name,
Supervisor = GetDirector(d.Users.Where(u => u.InOdpgRole(OdpgUserRole.Director)).FirstOrDefault()),
ContactsSupervisor = GetContacts(d.Users.Where(u => u.InOdpgRole(OdpgUserRole.Director)).FirstOrDefault()),
Schedule = "C 9:00 18:00",
ReceptionContacts = await GetAddressAsync(d.Addresses.FirstOrDefault(d => d.AddressType == DepartmentAddressType.Reception), token)
});
return result;
}
private async Task<string> GetAddressAsync(DepartmentAddressDto? address, GetDepartmentsRequest token)
{
if (address != null)
{
var fullAddress = await _fiasApiClient.GetFullAddress(token.accessToken,
new ESOG.Fias.Api.Model.GetFullAddressRequest
{ BaseAddressId = address.FiasId, Building = address.Building, Room = address.Room });
//var res = JsonConvert.DeserializeObject<DepartmentAddress>(fullAddress);
return fullAddress;
}
return "";
}
GetFederalDistrictCode, GetDirector, GetContacts - these methods are not asynchronous
It should just return a List<>, not Task<List<>>
Your call to departments.ConvertAll is returning a List<Task<DepartamentsResponse>>, but you want a Task<List<DepartamentsResponse>>.
Look at the Task.WhenAll method for how you would convert a collection of Tasks to a single Task that returns a collection. Then await that single Task and build your final list.
How to get the individual API call status success response in C#.
I am creating a mobile application using Xamarin Forms,
In my application, I need to prefetch certain information when app launches to use the mobile application.
Right now, I am calling the details like this,
public async Task<Response> GetAllVasInformationAsync()
{
var userDetails = GetUserDetailsAsync();
var getWageInfo = GetUserWageInfoAsync();
var getSalaryInfo = GetSalaryInfoAsync();
await Task.WhenAll(userDetails,
getWageInfo,
getSalaryInfo,
);
var resultToReturn = new Response
{
IsuserDetailsSucceeded = userDetails.Result,
IsgetWageInfoSucceeded = getWageInfo.Result,
IsgetSalaryInfoSucceeded = getSalaryInfo.Result,
};
return resultToReturn;
}
In my app I need to update details based on the success response. Something like this (2/5) completed. And the text should be updated whenever we get a new response.
What is the best way to implement this feature? Is it possible to use along with Task.WhenAll. Because I am trying to wrap everything in one method call.
In my app I need to update details based on the success response.
The proper way to do this is IProgress<string>. The calling code should supply a Progress<string> that updates the UI accordingly.
public async Task<Response> GetAllVasInformationAsync(IProgress<string> progress)
{
var userDetails = UpdateWhenComplete(GetUserDetailsAsync(), "user details");
var getWageInfo = UpdateWhenComplete(GetUserWageInfoAsync(), "wage information");
var getSalaryInfo = UpdateWhenComplete(GetSalaryInfoAsync(), "salary information");
await Task.WhenAll(userDetails, getWageInfo, getSalaryInfo);
return new Response
{
IsuserDetailsSucceeded = await userDetails,
IsgetWageInfoSucceeded = await getWageInfo,
IsgetSalaryInfoSucceeded = await getSalaryInfo,
};
async Task<T> UpdateWhenComplete<T>(Task<T> task, string taskName)
{
try { return await task; }
finally { progress?.Report($"Completed {taskName}"); }
}
}
If you also need a count, you can either use IProgress<(int, string)> or change how the report progress string is built to include the count.
So here's what I would do in C# 8 and .NET Standard 2.1:
First, I create the method which will produce the async enumerable:
static async IAsyncEnumerable<bool> TasksToPerform() {
Task[] tasks = new Task[3] { userDetails, getWageInfo, getSalaryInfo };
for (i = 0; i < tasks.Length; i++) {
await tasks[i];
yield return true;
}
}
So now you need to await foreach on this task enumerable. Every time you get a return, you know that a task has been finished.
int numberOfFinishedTasks = 0;
await foreach (var b in TasksToPerform()) {
numberOfFinishedTasks++;
//Update UI here to reflect the finished task number
}
No need to over-complicate this. This code will show how many of your tasks had exceptions. Your await task.whenall just triggers them and waits for them to finish. So after that you can do whatever you want with the tasks :)
var task = Task.Delay(300);
var tasks = new List<Task> { task };
var faultedTasks = 0;
tasks.ForEach(t =>
{
t.ContinueWith(t2 =>
{
//do something with a field / property holding ViewModel state
//that your view is listening to
});
});
await Task.WhenAll(tasks);
//use this to respond with a finished count
tasks.ForEach(_ => { if (_.IsFaulted) faultedTasks++; });
Console.WriteLine($"{tasks.Count() - faultedTasks} / {tasks.Count()} completed.");
.WhenAll() will allow you to determine if /any/ of the tasks failed, they you just count the tasks that have failed.
public async Task<Response> GetAllVasInformationAsync()
{
var userDetails = GetUserDetailsAsync();
var getWageInfo = GetUserWageInfoAsync();
var getSalaryInfo = GetSalaryInfoAsync();
await Task.WhenAll(userDetails, getWaitInfo, getSalaryInfo)
.ContinueWith((task) =>
{
if(task.IsFaulted)
{
int failedCount = 0;
if(userDetails.IsFaulted) failedCount++;
if(getWaitInfo.IsFaulted) failedCount++;
if(getSalaryInfo.IsFaulted) failedCount++;
return $"{failedCount} tasks failed";
}
});
var resultToReturn = new Response
{
IsuserDetailsSucceeded = userDetails.Result,
IsgetWageInfoSucceeded = getWageInfo.Result,
IsgetSalaryInfoSucceeded = getSalaryInfo.Result,
};
return resultToReturn;
}
The order of async-await operations in this bit of code is a bit off. The await Task inside DownloadMetasBySortAsync isn't behaving like I thought it should. The task completes itself, but only after DownloadMetasBySortAsync has already returned null to DownloadMetasAsync.
I've tried adding Task.Wait() on the tasks in DownloadMetasAsync and the offending, awaited task. I tried ConfigureAwait(). DownloadMetasBySortAsync always kicks back null before the needed task has been finished.
What am I missing?
public async Task<List<MyMeta>> DownloadMetasAsync(SortType sortType)
{
ResetFlags();
_cloudMetas = await DownloadMetasBySortAsync(sortType);
Debug.Log((_cloudMetas != null) ? _cloudMetas.Count + " metas downloaded" : "NULL cloudmetas");
return _cloudMetas;
}
private async Task<List<MyMeta>> DownloadMetasBySortAsync(SortType sortType)
{
//Load Table
Table dbTable = null;
//Can't call LoadTable from main thread
await Task.Run(() => {
try {
dbTable = Table.LoadTable(DBClient, new TableConfig(_databaseName));
}catch( Exception ex ) {
_operationFailed = true;
ThrowError(ex);
}
});
if(dbTable == null ) { return null; }
//Set up secondary local index if needed
string sortIndex = (sortType == SortType.Creator) ? "date-creator-index" : null;
return await ScanTable(_dbName, sortIndex); //Scan table for list of metas
}
private async Task<List<MyMeta>> ScanTable(string dbName, string index)
{
List<MyMeta> metaList = null;
try {
Dictionary<string,AttributeValue> lastKeyEvaluated = null;
do {
var request = new ScanRequest
{
TableName = dbName,
IndexName = index,
ConsistentRead = true,
ExclusiveStartKey = lastKeyEvaluated
};
Debug.Log("Scanning...");
await Task.Run(() =>
{
DBClient.ScanAsync(request, (responseObj) =>
{
if( responseObj == null ) {
Debug.LogWarning("Response NULL");
}
else {
Debug.Log("Response received");
if(metaList == null ) { metaList = new List<MyMeta>(); }
var resultList = ProcessScanResult(responseObj.Response);
if( resultList != null && resultList.Count > 0 ) {
metaList.AddRange(resultList);
}
}
lastKeyEvaluated = responseObj.Response.LastEvaluatedKey;
});
});
} while( lastKeyEvaluated != null && lastKeyEvaluated.Count != 0 );
}
catch( Exception ex ) {
ThrowError(ex);
}
return metaList;
}
Since the DBClient.ScanAsync is not an async/await method but a callback type of async method it will return immediately after being called and the results will only come later. To handle this you can use TaskCompletionSource to make it into awaitable thing:
var task = new TaskCompletionSource<responseObjType>();
DBClient.ScanAsync(request, (responseObj) =>
{
task.SetResult(responseObj);
}
// Will wait for the callback to be called before continuing and get the results
var responseObj = await task.Task;
if( responseObj == null )
{
...
This way the method will wait until the callback is called, the data is sent back via the TaskCompletionSource object and your code can process it further. This assumes you don't specifically want to run the rest of the code inside the callback, for threading purposes or anything, and will return to the main code flow to do the rest. You can also do the processing in the callback if you want.
I am adding a new web API call to existing functionality. I want to make this API call async but looks like it is causing deadlock. I have to make a lot more changes if I want to make entire code channel async which is not possible.
Questions I have are:
Is it possible to call async method from regular method?
What am I missing here? OR What is the correct approach here?
Code:
// Exisitng Method
public Tuple<RestaurantDeliveryProvider, DeliveryHubResult, Task<DeliveryManagerQuoteResponse>> CreateDeliveryRequest(OrderContextDTO orderContextDto)
{
var provider = RestaurantBl.GetDeliveryProviderInformationByRestaurantId(orderContextDto.RestaurantId ?? 0);
var deliveryHubResult = RestaurantBl.GetDeliveryHubResult(orderContextDto.OrderId ?? 0);;
// New Call which always comes back with "Not Yet Computed" result
Task<DeliveryManagerQuoteResponse> deliveryManagerQuoteResponse = _deliveryManager.CreateQuoteRequestAsync(orderContextDto, orderInfo);
return Tuple.Create(provider, deliveryHubResult, deliveryManagerQuoteResponse);
}
Async Methods:
public async Task<DeliveryManagerQuoteResponse> CreateQuoteRequestAsync(OrderContextDTO orderContextDto, OrderInfoDTO orderInfo)
{
DeliveryManagerQuoteResponse deliveryManagerQuoteResponse = null;
try
{
var restaurantInfo = RestaurantApi.GetRestaurant(orderInfo.RestaurantId);
var quoteRequest = new DeliveryManagerQuoteRequest
{
DeliveryProvider = null,
Country = orderContextDto.DeliveryEstimateRequestDto.RequestedDeliveryAddress.Country,
Concept = "BK",
StoreName = "BK-TEST-US-4",
OrderId = orderInfo.OrderId.ToString(),
AllowCash = false,
PaymentType = OrderPaymentType.Prepaid_Credit,
Note = orderInfo.DeliveryInstructions,
};
deliveryManagerQuoteResponse = await Quote(quoteRequest);
}
catch (Exception ex)
{
Log.ErrorFormat("Get Delivery Manager Quote failed: Error: {0}, OrderId: {1}", ex.Message, orderContextDto.OrderId);
}
return deliveryManagerQuoteResponse;
}
public async Task<DeliveryManagerQuoteResponse> Quote(DeliveryManagerQuoteRequest quoteRequest)
{
DeliveryManagerQuoteResponse deliveryManagerQuoteResponse;
var client = HttpClientFactory.GetClient();
var content = HttpClientFactory.JsonContentFactory.CreateJsonContent(quoteRequest);
var response = await client.PostAsync("https://myUrl", content);
if (response.IsSuccessStatusCode)
{
var data = await response.Content.ReadAsStringAsync();
deliveryManagerQuoteResponse = JsonConvert.DeserializeObject<DeliveryManagerQuoteResponse>(data);
}
else
{
throw new Exception((int)response.StatusCode + "-" + response.StatusCode);
}
return deliveryManagerQuoteResponse;
}
I tried following as well but same result:
public async Task<DeliveryManagerQuoteResponse> Quote(DeliveryManagerQuoteRequest quoteRequest)
{
DeliveryManagerQuoteResponse deliveryManagerQuoteResponse;
using (var client = new HttpClient())
{
var content = HttpClientFactory.JsonContentFactory.CreateJsonContent(quoteRequest);
var response = await client.PostAsync("https://myUrl", content);
if (response.IsSuccessStatusCode)
{
var data = await response.Content.ReadAsStringAsync();
deliveryManagerQuoteResponse = JsonConvert.DeserializeObject<DeliveryManagerQuoteResponse>(data);
}
else
{
throw new Exception((int)response.StatusCode + "-" + response.StatusCode);
}
}
return deliveryManagerQuoteResponse;
}
Output (sorry for the blurry output, if you click on it, you will see clear result):
don't
don't
Basically, there is no good or workable way to call an async method from a sync method and wait for the answer. There's "sync over async", but that's an anti-pattern and should be aggressively avoided.
So either:
rewrite the caller to be async
implement a synchronous version of the API