Using tasks

A task represents an operation that may or may not return a value and executes asynchronously. Since they are executed asynchronously, they are executed as worker threads from the thread pool rather than the primary thread. This allows us to use the isCanceled and IsCompleted properties to understand the state of the task. You can also make a task run synchronously, which will be executed on the main or primary thread.

A task can implement the IAsyncResult and IDisposable interfaces like so:

public class Task : IAsyncResult, IDisposable

Let's look at an example so that we can understand how we can create and initiate a task in different ways. In this example, we will use an action delegate that takes an argument of the object type:

public static void Run()
{
Action<object> action = (object obj) =>
{
Console.WriteLine("Task={0}, Milliseconds to sleep={1}, Thread={2}",Task.CurrentId, obj,
Thread.CurrentThread.ManagedThreadId);
int value = Convert.ToInt32(obj);
Thread.Sleep(value);
};

Task t1 = new Task(action, 1000);
Task t2 = Task.Factory.StartNew(action, 5000);
t2.Wait();
t1.Start();
Console.WriteLine("t1 has been started. (Main Thread={0})",
Thread.CurrentThread.ManagedThreadId);
t1.Wait();

int taskData = 4000;
Task t3 = Task.Run(() => {
Console.WriteLine("Task={0}, Milliseconds to sleep={1}, Thread={2}",
Task.CurrentId, taskData,
Thread.CurrentThread.ManagedThreadId);
});
t3.Wait();

Task t4 = new Task(action, 3000);
t4.RunSynchronously();
t4.Wait();
}

Here, we create four different tasks. For the first task, we used start methods, while for the second task, we used a task factory.startnew method. The third task was started using the run(Action) method, while the fourth task was executed synchronously on the main thread using the run synchronously method. Here, tasks 1, 2, and 3 are worker threads that are using a thread pool, while task 4 is executing on the primary thread.

The following screenshot shows the output of running the preceding code:

The Wait method is similar to Thread.Join, which waits until the task completes. This is useful when synchronizing the execution of calling threads and asynchronous tasks since we can wait for one or more threads to complete. The Wait method also accepts certain parameters that allow us to conditionally wait for a task to complete.

The following table shows the different options that are available for a thread when it comes to waiting:

Wait Waits for the task's execution to complete.
Wait(int32) Makes the tasks wait for a specified number of milliseconds before executing.
Wait(Timespan) Waits for the task's execution to complete within a specified time interval.
Wait(CancellationToken) Waits for the task's execution to complete. The wait is terminated if cancellationToken is issued before the task's execution is completed.
Wait(Int32, CancellationToken) Waits for the task's execution to complete. The wait terminates on timeout or when a cancellation token is issued before the task completes.
WaitAll Waits for all the provided tasks to complete their execution. Similar to the Wait method, WaitAll tasks multiple parameters and performs them accordingly.
WaitAny Waits for the provided task to complete its execution. Similar to the Wait method, WaitAll tasks multiple parameters and performs them accordingly.

 

Tasks support two other methods: WhenAll and WhenAny. Now, WhenAll is used to create a task that will complete its execution when all the provided tasks have been completed. Similarly, WhenAny creates tasks and completes when the provided task completes its execution.

A task can also return a value. However, reading the result of a task means waiting until its execution has completed. Without completing its execution, it isn't possible to use the result object. The following is an example of this:

public static void TaskReturnSample()
{
Task<int> t = Task.Run(() => { return 30 + 40; });
Console.WriteLine($"Result of 30+40: {t.Result}");
}

By executing the preceding code, you will see that the main thread waits until the task returns a value. Then, it displays a Press any key to exit message:

Result of 30+40: 70
Press any key to exit.

It's also possible to add a continuation task. .NET Framework provides a keyword called ContinueWith, which allows you to create a new task and execute it once the previous tasks have finished executing. In the following code, we are instructing the task to continue with the result from the parent task:

public static void TaskContinueWithSample()
{
Task<int> t = Task.Run(() =>
{
return 30 + 40;
}
).ContinueWith((t1) =>
{
return t1.Result * 10;
});
Console.WriteLine($"Result of two tasks: {t.Result}");
}

When task t has completed its execution, the result is used in the second task, t1, and the final result is displayed:

Result of two tasks: 700
Press any key to exit.

ContinueWith has a couple of overload methods that allow us to configure when the continuation task should execute, such as when a task is canceled or completed successfully. To make this configuration work, we will use TaskContinuationOptions. You can find more of the options that are available at https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcontinuationoptions?view=netframework-4.7.2.

The following code block shows how we can use continuationOptions:

Task<int> t = Task.Run(() => 
{
return 30 + 40;
}
).ContinueWith((t1) =>
{
return t1.Result * 10;
},TaskContinuationOptions.OnlyOnRanToCompletion);

TaskFactory supports creating and scheduling tasks. It also allows us to do the following:

  • Create a task and start it immediately using the StartNew method
  • Create a task that starts when any one of the tasks in an array has completed by calling the ContinueWhenAny method
  • Create a task that starts when all the tasks in an array have completed by calling the ContinueWhenAll method
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.142.166