async / await and Task / Wait in C# the same? - c#

I learned to use Task easily than async/await. Now, I'm trying to use Task to learn async/await.
static void Main(string[] args)
{
Console.ReadKey(true);
//Magic1();
Magic2();
Console.WriteLine("{0}", DateTime.Now.ToString());
Console.ReadKey(true);
}
static async void Magic1()
{
var taskA = GetDataAsync();
var taskB = GetDataAsync();
var taskC = GetDataAsync();
Console.WriteLine("a: " + await taskA);
Console.WriteLine("b: " + await taskB);
Console.WriteLine("c: " + await taskC);
}
static Task Magic2()
{
return Task.Run(() =>
{
var taskA = GetDataAsync();
var taskB = GetDataAsync();
var taskC = GetDataAsync();
Task.WaitAll(new Task[] { taskA, taskB, taskC });
Console.WriteLine("a: " + taskA.Result);
Console.WriteLine("b: " + taskB.Result);
Console.WriteLine("c: " + taskC.Result);
});
}
static Task<string> GetDataAsync()
{
return Task.Run(() =>
{
var startTime = DateTime.Now;
for (var i = 0; i < 1000000000; i++)
{
}
var endTime = DateTime.Now;
return startTime.ToString() + " to " + endTime.ToString() + " is " + (endTime - startTime).ToString();
});
}
I created two methods that appears to do the same thing, my questions are:
1) is Magic1 and Magic2 the same under the hood?
2) if they are not the same, can I convert Magic1 to a method that does the same thing without using async and await keywords?

Now, I'm trying to use my knowledge in Task to learn async/await.
I really recommend you not do this. Although task-based parallelism (.NET 4.0) and the task-based asynchronous pattern (async/await) use the same type (Task), they are completely different. They solve different problems and have a different way of working.
Instead, I suggest you pretend that you know nothing about the Task type and start with my async intro. At the end of my async intro are several followup resources, including the MSDN docs which are quite good for this feature.
If you're really familiar with continuations, you can (mostly) think of await as meaning "rewrite the rest of this method as a continuation and schedule it using the current SynchronizationContext or TaskScheduler". But even that is only an approximation; there are plenty of edge cases. It is not at all the same as doing a Task.Wait[All] within a Task.Run.

Differences between the two methods:
Where their code runs. Magic2 will run on the threadpool instead of in the caller's synchronization context, because you (unnecessarily) used Task.Run. If you were writing UI code, this could cause Magic2 to fail.
Blocking. Magic1 blocks the caller until it is done. This is generally a mistake when writing async code. Magic2 won't block the caller, even if you remove the Task.Run.
Incremental work. If taskB takes significantly longer to complete, that won't delay Magic2 from printing the result of taskA. Magic1 waits for all three tasks to finish, so it won't print taskA until after taskB finished.

Related

Practical example of async await does not meet expectations

Related to this question: Does await completely blocks the thread?
[...] it will first check to see if the called method completed, and if not will register the continuation and return from that method call. Later, once that method completes, it will re-enter the state-machine in order to complete the method
And to this question also: When is the best place to use Task.Result instead of awaiting Task
await simply means "this workflow cannot progress further until this task is completed, so if it is not complete, find more work to do and come back later"
And finally to this post: https://blog.stephencleary.com/2012/02/async-and-await.html
If “await” sees that the awaitable has not completed, then it acts asynchronously. It tells the awaitable to run the remainder of the method when it completes, and then returns from the async method. Later on, when the awaitable completes, it will execute the remainder of the async method. If you’re awaiting a built-in awaitable (such as a task), then the remainder of the async method will execute on a “context” that was captured before the “await” returned.
So from these posts I get that the await operator does indeed not block, but when I've tried to test it i just cannot get this principle to work the way it states to work. Obviously I'm missing something:
//This will take 10 seconds
[HttpGet("test1")]
public async Task<TimeSpan> test()
{
var t1 = DateTime.Now;
var wait1 = DoAsyncEcho("The first!", 10000);
var wait2 = DoAsyncEcho("The second!", 10000);
_logger.LogInformation(await wait1);
_logger.LogInformation(await wait2);
_logger.LogInformation("DONE!");
var t2 = DateTime.Now;
return t2 - t1;
}
//This will take 10 seconds too
[HttpGet("test2")]
public async Task<TimeSpan> test2()
{
var t1 = DateTime.Now;
var wait1 = DoAsyncEcho("The first!", 10000);
var wait2 = DoAsyncEcho("The second!", 10000);
Thread.Sleep(10000);
_logger.LogInformation(await wait1);
_logger.LogInformation(await wait2);
_logger.LogInformation("DONE!");
var t2 = DateTime.Now;
return t2 - t1;
}
//This will take 20
[HttpGet("test3")]
public async Task<TimeSpan> test3()
{
var t1 = DateTime.Now;
var wait1 = await DoAsyncEcho("The first!", 10000);
var wait2 = await DoAsyncEcho("The second!", 10000);
_logger.LogInformation(wait1);
_logger.LogInformation(wait2);
_logger.LogInformation("DONE!");
var t2 = DateTime.Now;
return t2 - t1;
}
//This will take 30
[HttpGet("test4")]
public async Task<TimeSpan> test4()
{
var t1 = DateTime.Now;
var wait1 = await DoAsyncEcho("The first!", 10000);
var wait2 = await DoAsyncEcho("The second!", 10000);
Thread.Sleep(10000);
_logger.LogInformation(wait1);
_logger.LogInformation(wait2);
_logger.LogInformation("DONE!");
var t2 = DateTime.Now;
return t2 - t1;
}
private Task<string> DoAsyncEcho(string v, int t)
{
return Task<string>.Factory.StartNew(() =>
{
Thread.Sleep(t);
return v;
}
);
}
As I see from the methods test3 and test4, await does indeed wait, it does not enter into a state-machine and does a callback later on because it waits the full 10 seconds of the first DoAsyncEcho and then another 10s on the second call. On the methods test1 and test2 execution time lasts for 10s as code does not waits for the return of the DoAsyncEcho but only it awaits for the result later. Particulary test2 method sleeps the 3 calls of 10 seconds in parallel so after all it's just a 10s run.
What do I'm missing here?
I think the best way to demonstrate this is through a simple Windows Forms app.
Create a default Windows Forms app and drop 3 buttons onto it (called button1, button2 and button3.
Then add the following code:
async void button1_Click(object sender, EventArgs e)
{
this.Text = "[button1_Click] About to await slowMethodAsync()";
int result = await slowMethodAsync();
this.Text = "[button1_Click] slowMethodAsync() returned " + result;
}
void button2_Click(object sender, EventArgs e)
{
this.Text = "[button2_Click] About to start task to call slowMethod()";
int result = 0;
Task.Run(() =>
{
result = slowMethod();
}).ContinueWith(_ =>
{
this.Invoke(new Action(() =>
{
this.Text = "[button2_Click] slowMethod() returned " + result;
}));
});
}
void button3_Click(object sender, EventArgs e)
{
this.Text = "[button3_Click] About to call slowMethod()";
int result = slowMethod();
this.Text = "[button3_Click] slowMethod() returned " + result;
}
static async Task<int> slowMethodAsync()
{
await Task.Delay(5000);
return 42;
}
static int slowMethod()
{
Thread.Sleep(5000);
return 42;
}
If you try this code out, you will notice the following:
Pressing button1 will immediately change the title to [button1_Click] About to await Task.Delay(5000), and you can resize the dialog while waiting for 5 seconds, after which the title will change to [button1_Click] Awaited Task.Delay(5000).
The code for handling button2 is very roughly equivalent to the state machine that is generated from the await code for button1. If you press button2, you will see similar effects to pressing button1.
(The actual code for await is in reality quite different, but the underlying mechanism of using a continuation - i.e., ContinueWith() and Invoke(), to continue executing the code after the await on the UI thread illustrates its approach.)
The code for button3 completely blocks during the Thread.Sleep(), and if you press button3 the UI locks up completely for 5 seconds,.
To illustrate what happens with a non-UI example, consider the following console application:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Demo
{
static class Program
{
static async Task Main()
{
Console.WriteLine("Main thread ID = " + Thread.CurrentThread.ManagedThreadId);
int result = slowMethod();
Console.WriteLine("result = " + result);
Console.WriteLine("After calling slowMethod(), thread ID = " + Thread.CurrentThread.ManagedThreadId);
result = await slowMethodAsync();
Console.WriteLine("result = " + result);
Console.WriteLine("After calling slowMethodAsync(), thread ID = " + Thread.CurrentThread.ManagedThreadId);
}
static async Task<int> slowMethodAsync()
{
await Task.Delay(5000);
return 42;
}
static int slowMethod()
{
Thread.Sleep(5000);
return 42;
}
}
}
If you run that, you will see output similar to the following:
Main thread ID = 1
result = 42
After calling slowMethod(), thread ID = 1
result = 42
After calling slowMethodAsync(), thread ID = 4
Note how the code has resumed on a different thread after the await.
The key thing to realise is that as far as calling code is concerned, y = await X(); does not return until it has a value to return, and the code that runs afterwards may be running on a different thread.
The effect of this in terms of blocking THREADS is that the calling thread is freed up to go off and execute some other code, and another thread is only required when the async method returns.
In many cases, this means that no additional thread is required (for the continuation), and in all cases it means that the original calling thread is not blocked and can be freed up to the thread pool for use for another task.
This is the "non blocking" part of all this.
For a good, detailed explanation of why sometimes no additional thread is needed, read Stephen Cleary's excellent "There is no thread".
It seems that you are confusing two different interpretations of wait and block. The objective of asynchronous code is to block your code, while the thread remains unblocked. If you don't want to block your code, then the solution is easy: don't use await. But if you don't block your code, then you can't use the result of the asynchronous operation, because the asynchronous operation is running concurrently with your code.
What has not happened yet belongs to the future, and the future is unknown. Not only do you not know the result, you don't even know if the operation succeeded or failed. In most cases this is problematic. You need the result of the operation before continuing with processing this result. So you must block your code. And this is why await was invented, to block your code without having to block a thread too.
You need the thread to remain unblocked, so that it continues running the UI message pump that keeps your application responsive. Just because your code is blocked, your _application) need not to be blocked also. For ASP.NET applications, you need the thread to remain unblocked so that it can serve other incoming web requests. The fewer threads you block, the more requests you can serve. In this case async/await becomes a booster of scalability.

How to retrieve the out parameter of an asynchronous method that runs on another thread

I created a class Out<T>, intending to use it as an out parameter in async methods.
class Out<T>
{
public T Value { get; set; }
}
Here is an example of how I intend to use it:
async Task DoWorkAsync(Out<int> arg)
{
arg.Value = 13;
await Task.Delay(500); // Placeholder for a really useful async operation
}
The Value will always be assigned before the first await inside the asynchronous method. In other words the assignment will happen in the synchronous part of the method. So the caller can retrieve the value immediately after creating the Task, and use it before the awaiting of the Task:
var arg = new Out<int>();
var task = DoWorkAsync(arg);
Console.WriteLine(arg.Value); // Prints '13' in the console :-)
await task;
Until this point everything works perfectly. The problem appears when it becomes mandatory for the DoWorkAsync method to run in a ThreadPool thread. Initially I tried the simplest approach with Task.Run:
var arg = new Out<int>();
var task = Task.Run(() => DoWorkAsync(arg));
Console.WriteLine(arg.Value); // Prints '0' in the console :-(
await task;
This didn't work. Another try:
var arg = new Out<int>();
var task = Task.Run(async () => await DoWorkAsync(arg));
Console.WriteLine(arg.Value); // Prints '0' in the console :-(
await task;
Didn't work either. After much thought I realized that the current thread cannot retrieve the arg.Value until the ThreadPool thread has processed the synchronous part of the method, so the current thread must wait. Then, after retrieving the arg.Value, the current thread must wait one more time for the asynchronous part of the method. This led me to a solution of the problem:
var arg = new Out<int>();
var task = await Task.Factory.StartNew(() => DoWorkAsync(arg),
CancellationToken.None, TaskCreationOptions.DenyChildAttach,
TaskScheduler.Default);
Console.WriteLine(arg.Value); // Prints '13' in the console :-/
await task;
This worked, but I dislike the use of Task.Factory.StartNew. This is an old-school method, with a bad reputation of being dangerous. So my question is: Is it possible to solve this problem, without using the Task.Factory.StartNew method?
Clarification: The reason it is mandatory for the DoWorkAsync method to run in a ThreadPool thread is because it contains a call to a third-party API that I don't trust, and so by calling it directly in the UI thread I risk freezing the UI. So my goal is to retrieve the Out.Value immediately after the DoWorkAsync returns, without invoking it in the current thread.
Your Task.Run needs Wait() in order to wait for the task to be completed.
A quick test with Stopwatch will give us an idea :
Stopwatch sw = new Stopwatch();
var arg = new Out<int>();
sw.Start();
var task = Task.Run(() => DoWorkAsync(arg));
sw.Stop();
Console.WriteLine("Elapsed: " + sw.ElapsedMilliseconds + "ms");
Console.WriteLine(arg.Value); // Prints '0' in the console :-(
The results :
Elapsed: 11ms
0
as you can see, the Task.Run didn't wait for the Task.Delay(500).
while if you use Wait()
Stopwatch sw = new Stopwatch();
var arg = new Out<int>();
sw.Start();
Task.Run(() => DoWorkAsync(arg)).Wait();
sw.Stop();
Console.WriteLine("Elapsed: " + sw.ElapsedMilliseconds);
Console.WriteLine(arg.Value); // Prints '0' in the console :-(
results :
Elapsed: 518ms
13
it waited for the task to be completed.
compare it with asynchronously wait:
Stopwatch sw = new Stopwatch();
var arg = new Out<int>();
sw.Start();
var task = DoWorkAsync(arg);
await task;
sw.Stop();
Console.WriteLine("Elapsed: " + sw.ElapsedMilliseconds);
Console.WriteLine(arg.Value); // Prints '13' in the console :-)
results :
Elapsed: 516
13

Async/Await single thread/some threads

I need a little rule about correct usage of await. Run this code in .net core c# 7.2:
static class Program
{
static async Task<string> GetTaskAsync(int timeout)
{
Console.WriteLine("Task Thread: " + Thread.CurrentThread.ManagedThreadId);
await Task.Delay(timeout);
return timeout.ToString();
}
static async Task Main()
{
Console.WriteLine("Main Thread: " + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("Should be greater than 5000");
await Watch(NotParallel);
Console.WriteLine("Should be less than 5000");
await Watch(Parallel);
}
public static async Task Parallel()
{
var res1 = GetTaskAsync(2000);
var res2 = GetTaskAsync(3000);
Console.WriteLine("result: " + await res1 + await res2);
}
public static async Task NotParallel()
{
var res1 = await GetTaskAsync(2000);
var res2 = await GetTaskAsync(3000);
Console.WriteLine("result: " + res1 + res2);
}
private static async Task Watch(Func<Task> func) {
var sw = new Stopwatch();
sw.Start();
await func?.Invoke();
sw.Stop();
Console.WriteLine("Elapsed: " + sw.ElapsedMilliseconds);
Console.WriteLine("---------------");
}
}
As you all can see the behavior of two methods are different. It's easy to get wrong in practice. So i need a "thumb rule".
Update for real men Please, run code. And explain please why Parallel() runs faster than NonParallel().
While calling GetTaskAsync without await, you actually get a Task with the method to execute (that is, GetTaskAsync) wrapped in. But when calling await GetTaskAsync, execution is suspended until the method is done executing, and then you get the result.
Let me be more clear:
var task = GetTaskAsync(2000);
Here, task is of type Task<string>.
var result = await GetTaskAsync(2000);
Here result is of type string.
So to address your first interrogation: when to await your Tasks really depends on your execution flow.
Now, as to why Parallel() is faster, I suggest your read this article (everything is of interest, but for your specific example, you may jump to Tasks return "hot").
Now let's break it down:
The await keyword serves to halt the code until the task is completed,
but doesn't actually start it.
In your example, NotParallel() will take longer because your Tasks execute sequentially, one after the other. As the article explains:
This is due to the tasks being awaited inline.
In Parallel() however...
the tasks now run in parallel. This is due to the fact that all [tasks]
are started before all [tasks] are subsequently awaited, again, because
they return hot.
About 'hot' tasks
I suggest your read the following: Task-based Asynchronous Pattern (TAP)
The Task Status section is of interest here to understand the concepts of cold and hot tasks:
Tasks that are created by the public Task constructors are referred to as cold tasks, because they begin their life cycle in the non-scheduled Created state and are scheduled only when Start is called on these instances.
All other tasks begin their life cycle in a hot state, which means that the asynchronous operations they represent have already been initiated
I invite you to read extensively about async/await and Tasks. Here are a few resources in addition to the ones I provided above:
Asynchronous Programming in C# 5.0 part two: Whence await?
Async/Await - Best Practices in Asynchronous Programming
Async and Await

Loop with Asyn and await [duplicate]

I've been trying for a while to get something I thought would be simple working with .NET 4.5
I want to fire off two long running tasks at same time and collect the
results in in the best C# 4.5 (RTM) way
The following works but I don't like it because:
I want Sleep to be an async method so it can await other methods
It just looks clumsy with Task.Run()
I don't think this is even using any new language features at all!
Working code:
public static void Go()
{
Console.WriteLine("Starting");
var task1 = Task.Run(() => Sleep(5000));
var task2 = Task.Run(() => Sleep(3000));
int totalSlept = task1.Result + task2.Result;
Console.WriteLine("Slept for a total of " + totalSlept + " ms");
}
private static int Sleep(int ms)
{
Console.WriteLine("Sleeping for " + ms);
Thread.Sleep(ms);
Console.WriteLine("Sleeping for " + ms + " FINISHED");
return ms;
}
Non working code:
Update: This actually works and is the correct way to do it, the only problem is the Thread.Sleep
This code doesn't work because the call to Sleep(5000) immediately starts the task running so Sleep(1000) doesn't run until it has completed. This is true even though Sleep is async and I'm not using await or calling .Result too soon.
I thought maybe there is a way to get a non-running Task<T> by calling an async method so I could then call Start() on the two tasks, but I can't figure out how to get a Task<T> from calling an async method.
public static void Go()
{
Console.WriteLine("Starting");
var task1 = Sleep(5000); // blocks
var task2 = Sleep(1000);
int totalSlept = task1.Result + task2.Result;
Console.WriteLine("Slept for " + totalSlept + " ms");
}
private static async Task<int> Sleep(int ms)
{
Console.WriteLine("Sleeping for " + ms);
Thread.Sleep(ms);
return ms;
}
async Task<int> LongTask1() {
...
return 0;
}
async Task<int> LongTask2() {
...
return 1;
}
...
{
Task<int> t1 = LongTask1();
Task<int> t2 = LongTask2();
await Task.WhenAll(t1,t2);
//now we have t1.Result and t2.Result
}
You should use Task.Delay instead of Sleep for async programming and then use Task.WhenAll to combine the task results. The tasks would run in parallel.
public class Program
{
static void Main(string[] args)
{
Go();
}
public static void Go()
{
GoAsync();
Console.ReadLine();
}
public static async void GoAsync()
{
Console.WriteLine("Starting");
var task1 = Sleep(5000);
var task2 = Sleep(3000);
int[] result = await Task.WhenAll(task1, task2);
Console.WriteLine("Slept for a total of " + result.Sum() + " ms");
}
private async static Task<int> Sleep(int ms)
{
Console.WriteLine("Sleeping for {0} at {1}", ms, Environment.TickCount);
await Task.Delay(ms);
Console.WriteLine("Sleeping for {0} finished at {1}", ms, Environment.TickCount);
return ms;
}
}
While your Sleep method is async, Thread.Sleep is not. The whole idea of async is to reuse a single thread, not to start multiple threads. Because you've blocked using a synchronous call to Thread.Sleep, it's not going to work.
I'm assuming that Thread.Sleep is a simplification of what you actually want to do. Can your actual implementation be coded as async methods?
If you do need to run multiple synchronous blocking calls, look elsewhere I think!
To answer this point:
I want Sleep to be an async method so it can await other methods
you can maybe rewrite the Sleep function like this:
private static async Task<int> Sleep(int ms)
{
Console.WriteLine("Sleeping for " + ms);
var task = Task.Run(() => Thread.Sleep(ms));
await task;
Console.WriteLine("Sleeping for " + ms + "END");
return ms;
}
static void Main(string[] args)
{
Console.WriteLine("Starting");
var task1 = Sleep(2000);
var task2 = Sleep(1000);
int totalSlept = task1.Result +task2.Result;
Console.WriteLine("Slept for " + totalSlept + " ms");
Console.ReadKey();
}
running this code will output :
Starting
Sleeping for 2000
Sleeping for 1000
*(one second later)*
Sleeping for 1000END
*(one second later)*
Sleeping for 2000END
Slept for 3000 ms
It's weekend now!
public async void Go()
{
Console.WriteLine("Start fosterage...");
var t1 = Sleep(5000, "Kevin");
var t2 = Sleep(3000, "Jerry");
var result = await Task.WhenAll(t1, t2);
Console.WriteLine($"My precious spare time last for only {result.Max()}ms");
Console.WriteLine("Press any key and take same beer...");
Console.ReadKey();
}
private static async Task<int> Sleep(int ms, string name)
{
Console.WriteLine($"{name} going to sleep for {ms}ms :)");
await Task.Delay(ms);
Console.WriteLine("${name} waked up after {ms}ms :(";
return ms;
}
This article helped explain a lot of things. It's in FAQ style.
Async/Await FAQ
This part explains why Thread.Sleep runs on the same original thread - leading to my initial confusion.
Does the “async” keyword cause the invocation of a method to queue to
the ThreadPool? To create a new thread? To launch a rocket ship to
Mars?
No. No. And no. See the previous questions. The “async” keyword
indicates to the compiler that “await” may be used inside of the
method, such that the method may suspend at an await point and have
its execution resumed asynchronously when the awaited instance
completes. This is why the compiler issues a warning if there are no
“awaits” inside of a method marked as “async”.

Number of threads required for IO bound async work

TL;DR:
Is it possible to kick off a series of IO bound tasks using only one thread using async await?
In less brief:
Trying to learn async await. In this video ("Async Best Practices for C# and Visual Basic"), the speaker gives an example of using async await to kick off some IO bound work. He explicitly says (at 21m 40s) whilst explaining why parallel for loops are not optimal as they use up loads of threads:
We don't need more threads for this. We don't need two threads...
Can we really kick off multiple requests asyncronously without using more than one thread? How? Unfortunately, the speaker didn't provide all the code so here's my stab at it:
// Pretty much exactly the same as video
private async Task<List<string>> LoadHousesAsync()
{
// Running on the UI thread
Debug.Print("Thread: " + Thread.CurrentThread.ManagedThreadId);
var tasks = new List<Task<string>>();
for (int i = 0; i < 5; i++)
{
Task<string> t = LoadHouseAsync(i);
tasks.Add(t);
}
string[] loadedHouses = await Task.WhenAll(tasks);
return loadedHouses.ToList();
}
// My guess of the LoadHouseAsync method
private Task<string> LoadHouseAsync(int i)
{
// Running on the UI thread
Debug.Print("Thread: " + Thread.CurrentThread.ManagedThreadId);
return Task.Run(() => LoadHouse(i));
}
// My guess of the LoadHouse method
private string LoadHouse(int i)
{
// **** This is on a different thread :( ****
Debug.Print("Thread: " + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(5000); // simulate I/O bound work
return "House" + i;
}
Here's the output.
Thread: 10
Thread: 10
Thread: 3
Thread: 10
Thread: 10
Thread: 11
Thread: 10
Thread: 12
Thread: 10
Thread: 13
Thread: 14
You can do it with async I/O. What you made is a very nice example of doing it wrong (unfortunately, it's also quite common).
Task.Run runs a method on a thread pool thread, Thread.Sleep blocks the thread. So your example simulates doing synchronous (blocking) I/O on multiple threads.
To correctly perform async I/O, you need to use async methods all the way down. Never use Task.Run for I/O. You can simulate an asynchronous I/O method using Task.Delay:
private async Task<string> LoadHouseAsync(int i)
{
Debug.Print("Thread: " + Thread.CurrentThread.ManagedThreadId);
await Task.Delay(5000); // simulate async I/O bound work
return "House" + i;
}
Can we really kick off multiple requests asyncronously without using more than one thread?
Yes, here is a real world example (pseudo asp.net-mvc and entity framework, where EF will make IO calls to Sql Server for example).
public async ActionResult()
{
var model = new Company();
using (var db1 = new DbContext)
using (var db2 = new DbContext)
{
var task1 = db1.Employees.ToListAsync();
var task2 = db1.Managers.ToListAsync();
await Task.WhenAll(task1, task2);
model.employees = task1.Result;
model.managers = task2.Result;
}
return View(model);
}

Categories