Using WhenAll pattern in Unity via UniRx? - c#

When using the UniRx package in Unity, one can use async-await. But what's the right way to convert something like the following (this example uses a GPT-3 www request, but it could be anything)
string result1 = await textAI.GetCompletion("Albeit Einstein was");
string result2 = await textAI.GetCompletion("Susan Sarandon is");
to something that launches the GetCompletion functions simultaneously, then continues when all finished and returned their result? E.g. using pseudo-code, like this:
string result1 = null;
string result2 = null;
await Task.WhenAll({
result1 = await textAI.GetCompletion("Albeit Einstein was"),
result2 = await textAI.GetCompletion("Susan Sarandon is")
});
Debug.Log("Results: " + result1 + ", " + result2);
Thanks!

You could use the existing Task.WhenAll
async void Start()
{
Task<string> a = AsyncRoutineA(); // Here no await, you want the Task object
Task<string> b = AsyncRoutineB();
await Task.WhenAll(a, b); // add all tasks and await that one
Debug.Log(a.Result + " " + b.Result);
}
async Task<string> AsyncRoutineA()
{
await Task.Delay(1000);
return "A Done";
}
async Task<string> AsyncRoutineB()
{
await Task.Delay(2000);
return "B Done";
}
UniRx would do the same, with extra overhead so that you'd get an Observable object that you'd not use in this context.
You can do the same with coroutines:
IEnumerator Start()
{
string resultA = null;
string resultB = null;
StartCoroutine(AsyncRoutineA((result) => resultA = result));
StartCoroutine(AsyncRoutineA((result) => resultB = result));
yield return new WaitUntil(()=>
{
return !string.IsNullOrEmpty(resultA) && !string.IsNullOrEmpty(resultB);
});
Debug.Log(resultA + " " + resultB);
}
IEnumerator AsyncRoutineA(Action<string> result)
{
yield return new WaitForSeconds(1);
result.Invoke("A done");
}
IEnumerator AsyncRoutineB(Action<string> result)
{
yield return new WaitForSeconds(2f);
result.Invoke("B done");
}

Related

async and Tuple

I am trying to get my head around tuple for the first time using it in an async function. When testing I am not able to catch the response. I have been google the problem but not been able t solve it.
I want to run an async function that return three strings. I am not able to catch the response. Have tried referencing item or the name.
Here is the code:
var _moonAndSunResult = CleanSunAndMoonJson(_moonJson);
print(_moonAndSunResult);
Tried referencing the _moonAndSunResult in different ways.
// async Task<Tuple<string, string, string, string>> CleanSunAndMoonJson(string _json)
async Task<(string, string, string, string)> CleanSunAndMoonJson(string _json)
{
print("sunRise: " + GetCorrectTimeFormat("sunRise", _json));
print("sunSet: " + GetCorrectTimeFormat("sunSet", _json));
print("moonRise: " + GetCorrectTimeFormat("moonRise", _json));
print("moonSet: " + GetCorrectTimeFormat("moonSet", _json));
Utility.sunrise = GetCorrectTimeFormat("sunRise", _json);
Utility.sunset = GetCorrectTimeFormat("sunSet", _json);
Utility.moonrise = GetCorrectTimeFormat("moonRise", _json);
Utility.moonset = GetCorrectTimeFormat("moonSet", _json);
await Task.Yield();
//return (firstValue, secondValue, thirdValue);
//return new Tuple<string, string, string, string>(Utility.sunrise, Utility.sunset, Utility.moonrise, Utility.moonset);
return (Utility.sunrise, Utility.sunset, Utility.moonrise, Utility.moonset);
//return Tuple.Create(Utility.sunrise, Utility.sunset, Utility.moonrise, Utility.moonset);
}
I get this from the _moonAndSunResult print above
System.Threading.Tasks.Task`1[System.ValueTuple`4[System.String,System.String,System.String,System.String]]
What #YoungDeiza said (await the task), but really you don't need to use tasks here:
(string Sunrise, string Sunset, string Moonrise, string Moonset) CleanSunAndMoonJson(string _json)
{
...
var sunrise = GetCorrectTimeFormat("sunRise", _json);
var sunset = GetCorrectTimeFormat("sunSet", _json);
var moonrise = GetCorrectTimeFormat("moonRise", _json);
var moonset = GetCorrectTimeFormat("moonSet", _json);
return (sunrise, sunset, moonrise, moonset);
}
then
var _moonAndSunResult = CleanSunAndMoonJson(_moonJson);
You need to await the asynchronous method:
public static async Task Main()
{
var _moonAndSunResult = await CleanSunAndMoonJson(_moonJson);
print(_moonAndSunResult);
}
If you do not await it, you call print on a task not the result of the task.

I don't understand where I missed async\await

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 do I properly block code execution until callback

I am new to Unity and C# and trying to query my Firebase Realtime Database, but the code does not block for the callback to finish.
I have tried implementing callbacks but this does not seem to work.
static public void ReadFromDb(int level, Action<int> callback)
{
int return_value = -1;
string sessionId = PlayerPrefs.GetString("SessionID");
FirebaseDatabase.DefaultInstance.GetReference("users/"+sessionId).GetValueAsync().ContinueWith(task => {
if (task.IsFaulted)
{
// Handle the error...
Debug.Log("Task faulted");
callback(return_value);
}
else if (task.IsCompleted)
{
DataSnapshot snapshot = task.Result;
string score_string = (snapshot.Child(level.ToString()).Value).ToString();
Debug.Log("score_string " + score_string);
return_value = int.Parse(score_string);
callback(return_value);
}
});
}
public void LevelComplete()
{
DatabaseCode.writeDatabase(SceneManager.GetActiveScene().buildIndex + 1, counter);
DatabaseCode.ReadFromDb(SceneManager.GetActiveScene().buildIndex + 1, (result) => {
prevlevelscore = result;
Debug.Log("result " + result.ToString());
});
prevscore = prevlevelscore;
Debug.Log("Returned value: " + prevlevelscore.ToString());
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
In LevelComplete(), Debug.Log("Returned value: " + prevlevelscore.ToString()); executes before prevlevelscore = result;
I want to make sure that the value of prevlevelscore is updated before executing Debug.Log.
Put the rest of the code inside the callback too:
public void LevelComplete()
{
DatabaseCode.writeDatabase(SceneManager.GetActiveScene().buildIndex + 1, counter);
DatabaseCode.ReadFromDb(SceneManager.GetActiveScene().buildIndex + 1, (result) => {
Debug.Log("result " + result.ToString());
prevscore = result;
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
});
}
Your problem is that your ReadFromDb method returns before being finished. You can solve this issue by putting all your code in the callback (but you won't be able to do that all the time) or you can use the async await pattern.
Make ReadFromDb async:
static public async Task ReadFromDb(int level, Action<int> callback)
{
int return_value = -1;
string sessionId = PlayerPrefs.GetString("SessionID");
await FirebaseDatabase.DefaultInstance.GetReference("users/"+sessionId).GetValueAsync().ContinueWith(task => {
if (task.IsFaulted)
{
// Handle the error...
Debug.Log("Task faulted");
callback(return_value);
}
else if (task.IsCompleted)
{
DataSnapshot snapshot = task.Result;
string score_string = (snapshot.Child(level.ToString()).Value).ToString();
Debug.Log("score_string " + score_string);
return_value = int.Parse(score_string);
callback(return_value);
}
});
}
Note the await keywork before your GetValueAsync().ContinueWith because this precise code is asynchronous and needs to be awaited if you want to hold code execution until the result has been fetched.
And in your caller:
public async Task LevelComplete()
{
DatabaseCode.writeDatabase(SceneManager.GetActiveScene().buildIndex + 1, counter);
await DatabaseCode.ReadFromDb(SceneManager.GetActiveScene().buildIndex + 1, (result) => {
prevlevelscore = result;
Debug.Log("result " + result.ToString());
});
prevscore = prevlevelscore;
Debug.Log("Returned value: " + prevlevelscore.ToString());
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
This method becomes async as well (because asynchronicity propagates). Again, the await keyword will hold on execution until the readFromDb method has finished. Which means that your data will be ready.

Async getting data from different views of the same database

I want to fetch data from 5 different views of the same database asynchronously - I used the following solution:
public async Task<List<Product>> GetProductsAsync()
{
string query = $"SELECT * FROM dbo.v_Products";
try
{
Stopwatch sw = new Stopwatch();
sw.Start();
var items = await _dbContext.Database.SqlQuery<Product>(query).ToListAsync();
sw.Stop();
Debug.WriteLine("\t " + Thread.CurrentThread.ManagedThreadId + $" getting Products ({items.Count}) seconds: " + sw.Elapsed.TotalSeconds);
return items;
}
catch (Exception ex)
{
throw new Exception("Getting Products failed!", ex);
}
}
There is the following situation: I have ~30 databases, a thread is ran for each one and methods like "GetProductAsync" are executed for gathering data. But I haven' see improvement of using async, it seems that time of execution of each next method contains the time of execution of previous. Where could I be wrong?
UPD: Calling the function
public async Task<DataContext> GetDataAsync()
{
DataContext data = new DataContext();
var items1= await _dac.GetProductsAsync();
var items2 = await _dac.GetProducts2Async();
var items3 = await _dac.GetProducts3Async();
var items4 = await _dac.GetProducts4Async();
var items5 = await _dac.GetProducts5Async();
data.items1= items1;
data.items2= items2;
data.items3= items3;
data.items4= items4;
data.items5= items5;
return data;
}
Is it OK if I will recreate db context for each async method execution, like here?
public async Task<List<Product>> GetProductsAsync()
{
string query = $"SELECT * FROM dbo.v_Products";
var ctx = new myDbContext(_dbContext.Database.ConnectionString);
try
{
Stopwatch sw = new Stopwatch();
sw.Start();
var items = await ctx.Database.SqlQuery<Product>(query).ToListAsync();
sw.Stop();
Debug.WriteLine("\t " + Thread.CurrentThread.ManagedThreadId + $" getting Products ({items.Count}) seconds: " + sw.Elapsed.TotalSeconds);
return items;
}
catch (Exception ex)
{
throw new Exception("Getting Products failed!", ex);
}
}
Call all Async methods but Await all of them later,
public async Task<DataContext> GetDataAsync()
{
DataContext data = new DataContext();
var t1 = _dac.GetProductsAsync();
var t2 = _dac.GetProducts2Async();
var t3 = _dac.GetProducts3Async();
var t4 = _dac.GetProducts4Async();
var t5 = _dac.GetProducts5Async();
data.items1 = await t1;
data.items2 = await t2;
data.items3 = await t3;
data.items4 = await t4;
data.items5 = await t5;
return data;
}
Creating new context for each call is not going to create any problem as long as the execution time of database call is small. Each new connection uses/reuses connection from a connection pool so you should not eat up all of it.
Most probably because
Multiple active operations on the same context instance are not
supported.
Your calling method does exactly what it should do:
Use 'await' to ensure that any asynchronous operations have completed
before calling another method on this context.
https://msdn.microsoft.com/en-us/library/dn220262(v=vs.113).aspx
public async Task<DataContext> GetDataAsync()
{
DataContext data = new DataContext();
//crate individual tasks
var test1 = _dac.GetProductsAsync();
var test2 = _dac.GetProducts2Async();
var test3 = _dac.GetProducts3Async();
var test4 = _dac.GetProducts4Async();
var test5 = _dac.GetProducts5Async();
//Execute all tasks at once with WhenAll function
await Task.WhenAll(task1, task2, task3, task4, task5);
//This statement is executed only after all the tasks are finished
return data;
}
Refer MSDN Link for detailed notes on WhenAll.

How to use await in a loop

I'm trying to create an asynchronous console app that does a some work on a collection. I have one version which uses parallel for loop another version that uses async/await. I expected the async/await version to work similar to parallel version but it executes synchronously. What am I doing wrong?
public class Program
{
public static void Main(string[] args)
{
var worker = new Worker();
worker.ParallelInit();
var t = worker.Init();
t.Wait();
Console.ReadKey();
}
}
public class Worker
{
public async Task<bool> Init()
{
var series = Enumerable.Range(1, 5).ToList();
foreach(var i in series)
{
Console.WriteLine("Starting Process {0}", i);
var result = await DoWorkAsync(i);
if (result)
{
Console.WriteLine("Ending Process {0}", i);
}
}
return true;
}
public async Task<bool> DoWorkAsync(int i)
{
Console.WriteLine("working..{0}", i);
await Task.Delay(1000);
return true;
}
public bool ParallelInit()
{
var series = Enumerable.Range(1, 5).ToList();
Parallel.ForEach(series, i =>
{
Console.WriteLine("Starting Process {0}", i);
DoWorkAsync(i);
Console.WriteLine("Ending Process {0}", i);
});
return true;
}
}
The way you're using the await keyword tells C# that you want to wait each time you pass through the loop, which isn't parallel. You can rewrite your method like this to do what you want, by storing a list of Tasks and then awaiting them all with Task.WhenAll.
public async Task<bool> Init()
{
var series = Enumerable.Range(1, 5).ToList();
var tasks = new List<Task<Tuple<int, bool>>>();
foreach (var i in series)
{
Console.WriteLine("Starting Process {0}", i);
tasks.Add(DoWorkAsync(i));
}
foreach (var task in await Task.WhenAll(tasks))
{
if (task.Item2)
{
Console.WriteLine("Ending Process {0}", task.Item1);
}
}
return true;
}
public async Task<Tuple<int, bool>> DoWorkAsync(int i)
{
Console.WriteLine("working..{0}", i);
await Task.Delay(1000);
return Tuple.Create(i, true);
}
Your code waits for each operation (using await) to finish before starting the next iteration.
Therefore, you don't get any parallelism.
If you want to run an existing asynchronous operation in parallel, you don't need await; you just need to get a collection of Tasks and call Task.WhenAll() to return a task that waits for all of them:
return Task.WhenAll(list.Select(DoWorkAsync));
public async Task<bool> Init()
{
var series = Enumerable.Range(1, 5);
Task.WhenAll(series.Select(i => DoWorkAsync(i)));
return true;
}
In C# 7.0 you can use semantic names to each of the members of the tuple, here is Tim S.'s answer using the new syntax:
public async Task<bool> Init()
{
var series = Enumerable.Range(1, 5).ToList();
var tasks = new List<Task<(int Index, bool IsDone)>>();
foreach (var i in series)
{
Console.WriteLine("Starting Process {0}", i);
tasks.Add(DoWorkAsync(i));
}
foreach (var task in await Task.WhenAll(tasks))
{
if (task.IsDone)
{
Console.WriteLine("Ending Process {0}", task.Index);
}
}
return true;
}
public async Task<(int Index, bool IsDone)> DoWorkAsync(int i)
{
Console.WriteLine("working..{0}", i);
await Task.Delay(1000);
return (i, true);
}
You could also get rid of task. inside foreach:
// ...
foreach (var (IsDone, Index) in await Task.WhenAll(tasks))
{
if (IsDone)
{
Console.WriteLine("Ending Process {0}", Index);
}
}
// ...
We can use async method in foreach loop to run async API calls.
public static void Main(string[] args)
{
List<ZoneDetails> lst = GetRecords();
foreach (var item in lst)
{
//For loop run asyn
var result = GetAPIData(item.ZoneId, item.fitnessclassid).Result;
if (result != null && result.EventHistoryId != null)
{
UpdateDB(result);
}
}
}
private static async Task<FODBrandChannelLicense> GetAPIData(int zoneId, int fitnessclassid)
{
HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = HttpClient.GetAsync(new Uri(url)).Result;
var content = response.Content.ReadAsStringAsync().Result;
var result = JsonConvert.DeserializeObject<Model>(content);
if (response.EnsureSuccessStatusCode().IsSuccessStatusCode)
{
Console.WriteLine($"API Call completed successfully");
}
return result;
}
To add to the already good answers here, it's always helpful to me to remember that the async method returns a Task.
So in the example in this question, each iteration of the loop has await. This causes the Init() method to return control to its caller with a Task<bool> - not a bool.
Thinking of await as just a magic word that causes execution state to be saved, then skipped to the next available line until ready, encourages confusion: "why doesn't the for loop just skip the line with await and go to the next statement?"
If instead you think of await as something more like a yield statement, that brings a Task with it when it returns control to the caller, in my opinion flow starts to make more sense: "the for loop stops at await, and returns control and the Task to the caller. The for loop won't continue until that is done."

Categories

Resources