Await multiple optional tasks - c#

Hello I'm wondering if there is a cleaner way of writing the async code below. Basically I want to wait on all the tasks, but one of the tasks are optional. It feels needlessly elaborate, thinking if I can do it through some callback but haven't been able to figure it out.
var mobile = true;
var task1 = _service.Async1();
var tasks = new List<Task>
{
task1
};
Task<int> task2 = null;
if (!mobile)
{
task2 = _service.Async2();
tasks.Add(task2);
}
await Task.WhenAll(tasks);
var result1 = task1.Result;
if (!mobile)
{
result2 = task2.Result;
// Do stuff
}

There is no need to create a list and await all the results at once. Why not await it when you need it? If task2 runs much longer than task1, you can at least start processing it, long before task2 is done.
Something like this:
var task1 = _service.Async1();
Task<int> task2 = null;
if (!mobile)
{
task2 = _service.Async2();
}
var result1 = await task1;
if (!mobile)
{
var result2 = await task2;
// Do stuff
}

Related

How do I replace the bot's message in the loop? DSharp-button

I would like to write some game in a bot in which the text changes very VERY often. But I don't understand how to do it without using declared new var
Please help form a permanent button jump, but without hard code
[Command("test_button")]
public async Task But(CommandContext ctx)
{
var button = await ctx.RespondAsync(m => m.WithContent("blya")
.AddComponents(new DiscordButtonComponent(ButtonStyle.Primary, "button-one", "button")));
var people = new string[] { "textAAA", "textBBB", "textCCC" };
var result = await button.WaitForButtonAsync();
await result.Result.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate).ConfigureAwait(false);
await button.ModifyAsync(people[0]).ConfigureAwait(false);
// how to fix it?
var result2 = await button.WaitForButtonAsync();
await result2.Result.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate).ConfigureAwait(false);
await button.ModifyAsync(people[1]).ConfigureAwait(false);
var result3 = await button.WaitForButtonAsync();
await result3.Result.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate).ConfigureAwait(false);
await button.ModifyAsync(people[2]).ConfigureAwait(false);
I thought it could be done through delegates or => but I'm still new to programming, so I don't understand how to do it
I would be very grateful for advice or hints
I just needed a loop "for", I'm groggy
for (int i = 0; i < 3; i++)
{
var result = await one.WaitForButtonAsync();
await result.Result.Interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate).ConfigureAwait(false);
await one.ModifyAsync(text[i]).ConfigureAwait(false);
}

C# - How to Both Await Tasks and Assign Values to Variables

I've been attempting to figure this out for a bit now but I can't seem to wrap my head around it. I have the following async task which calls other async tasks from my ClientFunctions object named "c".
public async Task RunAsync(Dictionary<int, Robots> botList)
{
this.botList = botList;
Task[] tasks = new Task[botList.Count]; //5 tasks for each bot, 5 bots total in a list
for (int i = 0; i < botList.Count; i++)
{
tasks[i] = botList[i].c.StartClient();
await tasks[i];
tasks[i] = botList[i].c.setConnection();
}
await Task.WhenAll(tasks);
Form1.Log("All done");
}
I await after StartClient() because it writes data to a shared file, and setConnection() reads the data from that file. I do this for all 5 bots.
The StartClient() function returns a Process, and I want to store that Process in each bot's class in a variable called "proc".
How would I go about storing the result while still being able to use a task array to wait for all 5 to complete?
Thanks.
Here's one possible implementation, assuming you want to StartClient on all bots sequentially and then call setConnection and await them all to finish.
public async Task RunAsync(Dictionary<int, Robots> botList)
{
this.botList = botList;
var tasks = new List<Task>();
foreach(var botKvp in botList)
{
var bot = botKvp.Value;
bot.proc = await bot.c.StartClient();
tasks.Add(bot.c.setConnection());
}
await Task.WhenAll(tasks);
Form1.Log("All done");
}
Task comes in two varieties: Task and Task<T>. You have an array of Task which does not define a return value. If you want to return a value you need to await a Task<T>. For instance if setConnection() should return a bool then it's signature should declare that as public Task<bool> setConnection(...)
Task[] tasks = new Task<Process>[botList.Count]
should be
Task<Process>[] tasks = new Task<Process>[botList.Count]
This works
bot.proc = await bot.c.StartClient();
Becuase StartClient() returns Task<Process> and await awaits that task and assigns the process to proc. As counter example this will fail:
Task procTask = bot.c.StartClient();
bot.proc = await procTask
When you await the task you get the result, so:
public async Task RunAsync(Dictionary<int, Robots> botList)
{
this.botList = botList;
Task[] tasks = new Task[botList.Count]; //5 tasks for each bot, 5 bots total in a list
for (int i = 0; i < botList.Count; i++)
{
tasks[i] = botList[i].c.StartClient();
botList[i].proc = await tasks[i];
tasks[i] = botList[i].c.setConnection();
}
await Task.WhenAll(tasks);
Form1.Log("All done");
}
If it were the setConnection() method that returned the item, the result of await Task.WhenAll(tasks) would hold a collection of items.

How to assign the results of WaitAll tasks to variables

I have a situation like this:
var retrievalTasks = new Task[2];
retrievalTasks[0] = GetNodesAsync();
retrievalTasks[1] = GetAssetsToHandleAsync();
Task.WaitAll(retrievalTasks);
And I would like the result of retrievalTasks[0] and retrievalTasks[1] to be stored in variables.
I could achieve this with:
var a = await GetNodesAsync();
var b = await GetAssetsToHandleAsync();;
But I would rather not await both like that, because then they're not fired at the same time right? Or am I misunderstanding?
I've seen this as a reference: Awaiting multiple Tasks with different results
But I think this is a slightly different scenario that wouldn't work in my case.
Any ideas?
Thanks
EDIT:
await Task.WhenAll(catTask, houseTask, carTask);
var cat = await catTask;
var house = await houseTask;
var car = await carTask;
This just seemed to wait four times to get the three results. However, #armenm has shown how to avoid that.
Here we go, you need to have separate variables for the tasks having respective types:
var task1 = GetNodesAsync();
var task2 = GetAssetsToHandleAsync();
Task.WaitAll(new Task[] { task1, task2 });
var result1 = task1.Result;
var result2 = task2.Result;
But I would recommend to make it asynchronous:
var task1 = GetNodesAsync();
var task2 = GetAssetsToHandleAsync();
await Task.WhenAll(new Task[] { task1, task2 });
var result1 = task1.Result;
var result2 = task2.Result;

Error handling for Tasks inside Task.WhenAll

I'm trying to create a web-scraper that queries a lot of urls in parallel and waits for their responses using Task.WhenAll(). However if one of the Tasks are unsuccessful, WhenAll fails. I am expecting many of the Tasks to return a 404 and wish to handle or ignore those. For example:
string urls = Enumerable.Range(1, 1000).Select(i => "https://somewebsite.com/" + i));
List<Task<string>> tasks = new List<Task<string>>();
foreach (string url in urls)
{
tasks.Add(Task.Run(() => {
try
{
return (new HttpClient()).GetStringAsync(url);
}
catch (HttpRequestException)
{
return Task.FromResult<string>("");
}
}));
}
var responseStrings = await Task.WhenAll(tasks);
This never hits the catch statement, and WhenAll fails at the first 404. How can I get WhenAll to ignore exceptions and just return the Tasks that completed successfully? Better yet, could it be done somewhere in the code below?
var tasks = Enumerable.Range(1, 1000).Select(i => (new HttpClient()).GetStringAsync("https://somewebsite.com/" + i))));
var responseStrings = await Task.WhenAll(tasks);
Thanks for your help.
You need to use await to observe the exception:
var tasks = Enumerable.Range(1, 1000).Select(i => TryGetStringAsync("https://somewebsite.com/" + i));
var responseStrings = await Task.WhenAll(tasks);
var validResponses = responseStrings.Where(x => x != null);
private async Task TryGetStringAsync(string url)
{
try
{
return await httpClient.GetStringAsync(url);
}
catch (HttpRequestException)
{
return null;
}
}

Regarding running multiple task & report when complete

i was looking for way out run multiple task and report about task status not sequencially.
here i am pasting a code where multiple task running and reporting when all task complete.
var task1 = Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
return "dummy value 1";
});
var task2 = Task.Factory.StartNew(() =>
{
Thread.Sleep(13000);
return "dummy value 2";
});
var task3 = Task.Factory.StartNew(() =>
{
Thread.Sleep(2000);
return "dummy value 3";
});
Task.Factory.ContinueWhenAll(new[] { task1, task2, task3 }, tasks =>
{
foreach (Task<string> task in tasks)
{
Console.WriteLine(task.Result);
}
});
Console.ReadLine();
task1 will take less time and then task3 will complete and last task2 will complete but ContinueWhenAll always showing task1 is completed and then task2 & task3. i want to modify the code in such way as a result which task complete fast that will show first not sequentially. where to change in code. guide please. thanks
UPDATE
var taskp = Task.Factory.StartNew(() =>
{
var task1 = Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
return "Task1 finished";
}).ContinueWith((continuation) => { Console.WriteLine(continuation.Result); });
var task2 = Task.Factory.StartNew(() =>
{
Thread.Sleep(3000);
return "Task2 finished";
}).ContinueWith((continuation) => { Console.WriteLine(continuation.Result); });
var task3 = Task.Factory.StartNew(() =>
{
Thread.Sleep(2000);
return "Task3 finished";
}).ContinueWith((continuation) => { Console.WriteLine(continuation.Result); });
return "Main Task finished";
});
taskp.ContinueWith(t => Console.WriteLine(t.Result));
Console.ReadLine();
An easy solution would be to ContinueWith each task, individually. This is a very quick and dirty example:
var taskp = Task.Factory.StartNew(() =>
{
var task1 = Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
return "dummy value 1";
}).ContinueWith((continuation) => { Console.WriteLine("task1"); });
var task2 = Task.Factory.StartNew(() =>
{
Thread.Sleep(3000);
return "dummy value 2";
}).ContinueWith((continuation) => { Console.WriteLine("task2"); });
var task3 = Task.Factory.StartNew(() =>
{
Thread.Sleep(2000);
return "dummy value 3";
}).ContinueWith((continuation) => { Console.WriteLine("task3"); });
Task.Factory.ContinueWhenAll(new Task[] { task1, task2, task3}, (x) => { Console.WriteLine("Main Task Complete"); });
});
The code is updated to adapt to OP's UPDATE, with a "Main" task which will output after all the "inner" tasks are complete. To make it so taskp returns the string instead of writing it, the code becomes
var taskp = Task.Factory.StartNew<string>(() =>
{
var task1 = ...; //same
var task2 = ...; //same
var task3 = ...; //same
return "Main Task Complete";
}).ContinueWith((x)=> Console.WriteLine(x.Result));
The StartNew<> overload must be used to specify the return type for the Task to have a Result.

Categories