SoFunction
Updated on 2025-03-06

The basics of the C# multi-threaded series (III)

TaskAwaiter

Let me talk firstTaskAwaiterTaskAwaiterDenotes an object waiting for the asynchronous task to complete and provides parameters for the result.

Task has aGetAwaiter()Method will returnTaskAwaiterorTaskAwaiter<TResult>TaskAwaiterType isDefined in the namespace.

TaskAwaiterThe properties and methods of types are as follows:

property:

property illustrate
IsCompleted Gets a value indicating whether the asynchronous task has been completed.

method:

method illustrate
GetResult() End the waiting for the asynchronous task to complete.
OnCompleted(Action) Set the operation to execute when the TaskAwaiter object stops waiting for the asynchronous task to complete.
UnsafeOnCompleted(Action) Plan the continuation of asynchronous tasks related to this awaiter.

Examples of use are as follows:

        static void Main()
        {
            Task&lt;int&gt; task = new Task&lt;int&gt;(()=&gt;
            {
                ("I'm a front-drive mission");
                ((1));
                return 666;
            });

            TaskAwaiter&lt;int&gt; awaiter = ();

            (()=&gt;
            {
                ("When the predecessor task is completed, I will continue to execute it");
            });
            ();

            ();
        }

In addition, we mentioned earlier that an unhandled exception occurs in a task and the task is terminated, which is also considered completing the task.

Another way to continue

We introduced it in the previous section.ContinueWith()Methods to achieve continuation, here we introduce another continuation method.ConfigureAwait()

.ConfigureAwait()If you want to try to marshall the continuation task back to the original context, thentrue; otherwisefalse

Let me explain,.ContinueWith()Continued tasks. After the current drive task is completed, the continued tasks will continue to be executed on this thread. This method is synchronous, with the former and the latter running continuously on one thread.

.ConfigureAwait(false)The method can be asynchronous. After the predecessor method is completed, the subsequent tasks can be ignored, and the subsequent tasks can be run on any thread. This feature is especially useful in UI interface programs.

You can refer to:/bynder-tech/c-why-you-should-use-configureawait-false-in-your-library-code-d7837dce3d7f

Its usage is as follows:

        static void Main()
        {
            Task&lt;int&gt; task = new Task&lt;int&gt;(()=&gt;
            {
                ("I'm a front-drive mission");
                ((1));
                return 666;
            });

            ConfiguredTaskAwaitable&lt;int&gt;.ConfiguredTaskAwaiter awaiter = (false).GetAwaiter();

            (()=&gt;
            {
                ("When the predecessor task is completed, I will continue to execute it");
            });
            ();

            ();
        }

ConfiguredTaskAwaitable<int>.ConfiguredTaskAwaiter Have andTaskAwaiterSame attributes and methods.

.ContinueWith()and.ConfigureAwait(false)Another difference is that the former can continue multiple tasks and tasks that continue tasks (multiple layers). The latter can only continue one layer of tasks (one layer can have multiple tasks).

Another way to create tasks

As mentioned earlier, there are three ways to create tasks:new Task()()(), Let’s learn the fourth method now:TaskCompletionSource<TResult>type.

Let's take a lookTaskCompletionSource<TResulr>Properties and methods of types:

property:

property illustrate
Task Gets the TaskCompletionSource created by this Task.

method:

method illustrate
SetCanceled() Convert the underlying Task to Canceled state.
SetException(Exception) Converts the underlying Task to a Faulted state and binds it to a specified exception.
SetException(IEnumerable) Convert the underlying Task to Faulted state and bind some exception objects to it.
SetResult(TResult) Converts the underlying Task to RanToCompletion state.
TrySetCanceled() Try to convert the underlying Task to a Canceled state.
TrySetCanceled(CancellationToken) Try to convert the underlying Task to Canceled state and enable the cancel tag to be stored in the canceled task.
TrySetException(Exception) Try to convert the underlying Task to a Faulted state and bind it to a specified exception.
TrySetException(IEnumerable) Try to convert the underlying Task to Faulted state and bind some exception objects to it.
TrySetResult(TResult) Try to convert the underlying Task to RanToCompletion state.

TaskCompletionSource<TResulr>Classes can control the life cycle of tasks.

First, pass.TaskAttribute, get oneTaskorTask<TResult> 。

            TaskCompletionSource<int> task = new TaskCompletionSource<int>();
            Task<int> myTask = ;	//  Task myTask = ;

Then pass()Method to controlmyTaskLife cycle, but myTask itself has no task content.

Examples of use are as follows:

        static void Main()
        {
            TaskCompletionSource&lt;int&gt; task = new TaskCompletionSource&lt;int&gt;();
            Task&lt;int&gt; myTask = ;       // task control myTask
            // Open a new task to do experiments            Task mainTask = new Task(() =&gt;
            {
                ("I can control myTask tasks");
                ("Press any key and I let the myTask task complete immediately");
                ();
                (666);
            });
            ();

            ("Start wait for myTask to return result");
            ();
            ("Finish");
            ();
        }

Others, for exampleSetException(Exception)The methods can be explored by yourself, so I won’t go into details here.

References:/premier-developer/the-danger-of-taskcompletionsourcet-class/

This article is well explained, and there are pictures:/gigilabs/taskcompletionsource-by-example/

Implement a type that supports synchronous and asynchronous tasks

This part of the content is correctTaskCompletionSource<TResult>Continue to explain.

Here we design a class similar to Task type that supports synchronous and asynchronous tasks.

  • Users can useGetResult()Synchronously obtain results;
  • Users can useRunAsync()Execute tasks, use.ResultAttributes get the result asynchronously;

It is implemented as follows:

/// &lt;summary&gt;
/// Implement the types of synchronous tasks and asynchronous tasks/// &lt;/summary&gt;
/// &lt;typeparam name="TResult"&gt;&lt;/typeparam&gt;
public class MyTaskClass&lt;TResult&gt;
{
    private readonly TaskCompletionSource&lt;TResult&gt; source = new TaskCompletionSource&lt;TResult&gt;();
    private Task&lt;TResult&gt; task;
    // Save the tasks that the user needs to perform    private Func&lt;TResult&gt; _func;

    // Whether the execution has been completed, synchronous or asynchronous execution is OK    private bool isCompleted = false;
    // Task execution results    private TResult _result;

    /// &lt;summary&gt;
    /// Get the execution result    /// &lt;/summary&gt;
    public TResult Result
    {
        get
        {
            if (isCompleted)
                return _result;
            else return ;
        }
    }
    public MyTaskClass(Func&lt;TResult&gt; func)
    {
        _func = func;
        task = ;
    }

    /// &lt;summary&gt;
    /// Synchronize method to get results    /// &lt;/summary&gt;
    /// &lt;returns&gt;&lt;/returns&gt;
    public TResult GetResult()
    {
        _result = _func.Invoke();
        isCompleted = true;
        return _result;
    }

    /// &lt;summary&gt;
    /// Execute tasks asynchronously    /// &lt;/summary&gt;
    public void RunAsync()
    {
        (() =&gt;
        {
            (_func.Invoke());
            isCompleted = true;
        });
    }
}

In the Main method, we create a task example:

    class Program
    {
        static void Main()
        {
            // Instantiate the task class            MyTaskClass&lt;string&gt; myTask1 = new MyTaskClass&lt;string&gt;(() =&gt;
            {
                ((1));
                return "";
            });

            // Get results directly synchronously            (());


            // Instantiate the task class            MyTaskClass&lt;string&gt; myTask2 = new MyTaskClass&lt;string&gt;(() =&gt;
            {
                ((1));
                return "";
            });

            // Get the results asynchronously            ();

            ();


            ();
        }
    }

()

Microsoft Documentation Explanation: Create a Task, which is completed by the cancel operation performed by the specified cancel tag.

Here I copied oneExample

var token = new CancellationToken(true);
Task task = (token);
Task<int> genericTask = <int>(token);

There are many such examples on the Internet, but what exactly is this thing used for? Just new?

Let's explore with questions, let's take an example:

        public static Task Test()
        {
            CancellationTokenSource source = new CancellationTokenSource();
            ();
            return &lt;object&gt;();
        }
        static void Main()
        {
            var t = Test();	// Set breakpoints here to monitor variables            ();
         }

()A cancelled task can be constructed. I've been searching for a long time and haven't found a good example. If a task is cancelled before it starts, then use()It's very good.

Here are many examples to refer to:/csharp-examples/()/

How to cancel a task internally

We have discussed before, usingCancellationTokenCancel the token pass parameters and cancel the task. But they are all delivered from the outside, so there is no need to implement it here.CancellationTokenYou can cancel the task.

We can useCancellationTokenofThrowIfCancellationRequested()Method throwException, then terminate the task, and the task will become canceled, but the task needs to pass in a token first.

Here I will design a more difficult thing, a class that can execute multiple tasks in order.

Examples are as follows:

    /// &lt;summary&gt;
    /// Asynchronous type that can complete multiple tasks    /// &lt;/summary&gt;
    public class MyTaskClass
    {
        private List&lt;Action&gt; _actions = new List&lt;Action&gt;();
        private CancellationTokenSource _source = new CancellationTokenSource();
        private CancellationTokenSource _sourceBak = new CancellationTokenSource();
        private Task _task;

        /// &lt;summary&gt;
        /// Add a task        /// &lt;/summary&gt;
        /// &lt;param name="action"&gt;&lt;/param&gt;
        public void AddTask(Action action)
        {
            _actions.Add(action);
        }

        /// &lt;summary&gt;
        /// Start executing tasks        /// &lt;/summary&gt;
        /// &lt;returns&gt;&lt;/returns&gt;
        public Task StartAsync()
        {
            // _ = new Task() is invalid for this example            _task = (() =&gt;
             {
                 for (int i = 0; i &lt; _actions.Count; i++)
                 {
                     int tmp = i;
                     ($"The {tmp} A task");
                     if (_source.)
                     {
                          = ;
                         ("The mission has been cancelled");
                          = ;
                         _sourceBak.Cancel();
                         _sourceBak.();
                     }
                     _actions[tmp].Invoke();
                 }
             },_sourceBak.Token);
            return _task;
        }

        /// &lt;summary&gt;
        /// Cancel the task        /// &lt;/summary&gt;
        /// &lt;returns&gt;&lt;/returns&gt;
        public Task Cancel()
        {
            _source.Cancel();

            // You can save it here            _task = &lt;object&gt;(_source.Token);
            return _task;
        }
    }

In the Main method:

        static void Main()
        {
            // Instantiate the task class            MyTaskClass myTask = new MyTaskClass();

            for (int i = 0; i &lt; 10; i++)
            {
                int tmp = i;
                (() =&gt;
                {
                    ("Task 1 Start");
                    ((1));
                    ("Task 1 End");
                    ((1));
                });
            }

            // Equivalent to ()            Task task = ();
            ((1));
            ($"Whether the task has been canceled:{}");

            // Cancel the task             = ;
            ("Press any key to cancel the task");
             = ;
            ();

            var t = ();    // Cancel the task            ((2));
            ($"Whether the task has been canceled:【{}】");

            ();
        }

You can cancel tasks at any stage.

Yield keywords

Iterator keywords make the data not need to be returned at once, and can iterate one by one when needed, which is also equivalent to asynchronous.

The iterator method runs toyield returnWhen a statement is made, aexpression, and retain its current position in the code. The next time the iterator function is called, execution will start again from that location.

Can be usedyield breakStatement to terminate iteration.

Official Documentation:/zh-cn/dotnet/csharp/language-reference/keywords/yield

Most of the examples online areforeachYes, some students don’t understand what this means. Let me briefly explain it here.

We can also write an example like this:

There is no more hereforeachNow.

        private static int[] list = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

        private static IEnumerable<int> ForAsync()
        {
            int i = 0;
            while (i < )
            {
                i++;
                yield return list[i];
            }
        }

However, the student asked again that the object returned by this return needs to implement thisIEnumerable<T>Is that OK? What does those documents talk about is iterator interfaces, and what are they?

We can change the example first:

        private static int[] list = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

        private static IEnumerable<int> ForAsync()
        {
            int i = 0;
            while (i < )
            {
                int num = list[i];
                i++;
                yield return num;
            }
        }

You call it in the Main method to see if it works normally?

        static void Main()
        {
            foreach (var item in ForAsync())
            {
                (item);
            }
            ();
        }

This means thatyield returnThe returned object does not need to be implementedIEnumerable<int>method.

actuallyyieldIt is a syntactic sugar keyword, you just need to call it in a loop.

        static void Main()
        {
            foreach (var item in ForAsync())
            {
                (item);
            }
            ();
        }

        private static IEnumerable<int> ForAsync()
        {
            int i = 0;
            while (i < 100)
            {
                i++;
                yield return i;
            }
        }
    }

It will be automatically generatedIEnumerable<T>, without you need to implement it firstIEnumerable<T> 。

Supplementary knowledge points

  • There are many methods for thread synchronization: Critical Section, Mutex, Semaphores, Events, Tasks;

  • ()and()Encapsulated Task;

  • ()yes()simplified form;

  • Some placesnet Task()is invalid; but()and()Can;

This is all about this article about the basics of the C# multi-threaded series of tasks (III). I hope it will be helpful to everyone's learning and I hope everyone will support me more.