Skip to content

Commit

Permalink
main
Browse files Browse the repository at this point in the history
  • Loading branch information
sharpchen committed Feb 14, 2025
1 parent 95fdaea commit 1115dd9
Show file tree
Hide file tree
Showing 4 changed files with 221 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Create & Wait & Cancel
# Start & Cancel

## Create & Start

Task can be created from the following sources:

- constructors of `Task` and `Task<T>`
- Task creation methods like `Task.Run` and `TaskFactory.StartNew`
- `async` methods
- `async` delegates

```cs
var task = new Task(() => Console.WriteLine("hello"));
Expand All @@ -28,9 +28,9 @@ _ = Task.Factory.StartNew(() => Console.WriteLine("hello"));
_ = Task.Run(() => Console.WriteLine("hello"));


Foo(); // started automatically
fooAsync(); // started automatically
static async void Foo()
var fooAsync = async () =>
{
await Task.Run(() => Console.WriteLine("foo"));
}
Expand Down Expand Up @@ -129,7 +129,7 @@ Task<int> t2 = await Task.Factory.StartNew(async () =>
```

> [!TIP]
> `Task.Run` does not require unwrap.
> `Task.Run` has a implicit auto-unwrap for some of its overloads
>```cs
>Task<int> t = Task.Run(async () =>
>{
Expand All @@ -149,6 +149,21 @@ Task<int> task = Task.FromResult(42);
Console.WriteLine(task.Result); // Outputs: 42
```
### Create as Continued Task

`Task.ContinueWith` creates a task after the previous task has terminated with certain condition, and it starts simultaneously.
Such condition can be specified by `TaskContinuationOptions` in its overloads.

```cs
Task.Run(async () =>
{
await Task.Delay(100);
}).ContinueWith(prev =>
{
Console.WriteLine("Continued task started automatically!");
}).Wait();
```

### `Task.Run` vs `TaskFactory.StartNew`

- `Task.Run`
Expand Down Expand Up @@ -180,58 +195,10 @@ _ = Task.Factory.StartNew(
);
```

## Blocking & Non-Blocking Wait

Waiting a task means the execution of code is synchronous but there's a essential difference between Blocking Wait & Non-Blocking Wait

- accessing for `task.Result` causing the blocking wait that blocks the main thread
```cs
Task<int> task = new(() => 123);

task.Start();

Console.WriteLine(task.Result); // compiler knows it should wait the task blockingly // [!code highlight]
// 123
```
- `await` operator waits the task without blocking the calling thread.
```cs
int foo = await Foo(); // does not block the main thread but still synchronous
static async Task<int> Foo
{
await Task.Delay(500);
return 123;
}
```

- `task.Wait`: to wait the task itself.
- `Task.Wait*`: utils to wait a batch of tasks in a **blocking** manner.
- `Task.When*`: utils to wait a batch of tasks in a **non-blocking** manner.


A task must be started before awaiting, or the thread would be blocked forever.
A task from `async` method is started implicitly so no worry here.

To wait a task synchronously, use `await` operator before a started task object.

```cs
var task = new Task(() => Console.WriteLine("hello"));
task.Start(); // must get it started!!! // [!code highlight]
await task;
```

Starting a simple task manually is quiet trivial so one should prefer `Task.Run` or `Task.Factory.StartNew`

```cs
await Task.Factory.StartNew((object? foo) =>
{
Console.WriteLine(((dynamic)foo).Foo);
}, new { Foo = 345 });

await Task.Run(() => { Console.WriteLine("hello"); });
```

## Creation Options

<!--TODO: creation options-->

## Task Cancellation

### What Manage it & What Represents it
Expand Down Expand Up @@ -265,7 +232,7 @@ Task task = new(() =>
{
if (token.IsCancellationRequested)
{
throw new OperationCanceledException(); // does not terminate the whole program // [!code highlight]
throw new OperationCanceledException(token); // does not terminate the whole program // [!code highlight]
}
Console.WriteLine("working with task");
}
Expand Down Expand Up @@ -301,7 +268,27 @@ So this checking on whether cancellation suceeded requires a validation on the t
<!--TODO: add example-->

```cs
using CancellationTokenSource cts = new(5000);
CancellationToken token = cts.Token;

Task task = Task.Factory.StartNew(() =>
{
while (true)
{
Console.WriteLine("operation...");
token.ThrowIfCancellationRequested();
Thread.Sleep(1000);
}
}, cts.Token); // it's oke, all tokens from same source are identical
try
{
task.Wait();
}
catch (AggregateException)
{
Console.WriteLine(task.Status); // Canceled
}
```

> [!NOTE]
Expand Down Expand Up @@ -364,6 +351,8 @@ Console.WriteLine("task finished");

After cancellation simply means chaining a event after previous task, and allowing access to previous task in the callback so you can do things conditionally.

<!--TODO: faulted example is not right, the task is not canceled correctly-->

```cs
var cts = new CancellationTokenSource();
var token = cts.Token;
Expand Down Expand Up @@ -395,6 +384,22 @@ Console.WriteLine("task finished");

You may wanted to combine tokens from different sources to perform a simultaneous cancellation even they're from different sources.

### Common Practice

```cs
using (CancellationTokenSource cts = new(timeout))
{
try
{
await task(foo, cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("canceled");
}
}
```

## Sleeping on Thread

- `Thread.Sleep`: pauses the thread and allows the scheduler to run another thread while sleeping, for the efficiency.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,22 @@ Task task2 = Task.Run(() =>
// program was not terminated
```

## What Might be Thrown

<!--TODO:might throw TaskCanceledException in some cases, does OperationCancelledException thrown?-->
Exception can be thrown and catched from a task for the following scenarios:

- `AggregateException` can be thrown from:
- `task.Wait();`
- `Task.Wait*`
- `task.Result`
- Direct exception can be thrown from:
- `await Task.When*`
- `await task`
- `task.GetAwaiter().GetResult()`

## Catch in Statement

Exception can be thrown and catched from a task for the following scenarios:
- `await task;`
- `task.Wait();`
- `task.Result`
- `task.GetAwaiter().GetResult()`

Exception yield from tasks is **always** a composite exception `AggregateException` **unless the exception is `OperationCancelledException` and the task has cancelled.**

Expand Down Expand Up @@ -88,13 +96,14 @@ catch (AggregateException ex)
}
```

## Access Exception From Task

## Handle in Continued Tasks

Task object itself can hold an `AggregateException` as a property.
So you may handle them in a continued task or a post operation.

```cs
_ = Task.Run(() => throw new AccessViolationException()).ContinueWith(prev =>
_ = Task.Factory.StartNew(() => throw new AccessViolationException()).ContinueWith(prev =>
{
prev.Exception?.Handle(iex =>
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Task Status

**One should always access tasks status in either `try..catch` statement or continued tasks**

## Created

Status `Created` is assigned when on task creation but usually seen on tasks created from constructors since other creation methods always start implicitly
Expand All @@ -9,23 +11,29 @@ Task task = new Task(() => { });
Console.WriteLine(task.Status); // Created
```

## WaitingForActivation

## WaitingForRun

A task has been scheduled by scheduler but has not yet behun execution
A task has been scheduled by scheduler but has not yet begun execution

## RunToCompletion
## RanToCompletion

Implying a task has completed successfully.
A task ran to end or terminated by returing a value has status `RunToCompletion`
A task ran to end or terminated by `return` or ran to end has status `RanToCompletion`

## Canceled

A successful cancellation happens **when all of the following were satisfied**
- `OperationCanceledException`(or its derived exception type) is thrown
A successful cancellation happens **when all of the three conditions were satisfied**
- `OperationCanceledException`(or its derived exception type such as `TaskCanceledException`) is thrown
- `token.IsCancellationRequested` is true
- `token` in closure passed to `OperationCanceledException` equals `token` as parameter
- `token` in closure passed to `OperationCanceledException` equals `token` as parameter on task creation

There's a special case that can result in Canceled status when a task requires unwrap.
If the inner task creation was Faulted because of a thrown of `OperationCanceledException`, the unwrap process would set the status of outer task to Canceled.
Otherwise it would just remain Faulted.

```cs
// compiler would choose a Task.Run(Func<Task> function) here
var task = Task.Run(() =>
{
throw new OperationCanceledException(); // [!code highlight]
Expand All @@ -39,18 +47,37 @@ catch (AggregateException)
{
Console.WriteLine(task.Status); // Cancelled // [!code highlight]
}
// Explicit unwrap have the same behavior
// async was marked here because TaskFactory.StartNew does not have such overload resolution like Task.Run
Task task2 = Task.Factory.StartNew(async () =>
{
await Task.Delay(100);
throw new Exception(); // it's another type of Exception this time // [!code highlight]
}).Unwrap();

try
{
task2.Wait();
}
catch (AggregateException)
{
Console.WriteLine(task2.Status); // Faulted // [!code highlight]
}
```

## Faulted

Faulted happens on:
Faulted happens on one of the scenarios:

- Any exception besides `OperationCanceledException` was thrown.
- `OperationCanceledException` is thrown && (`token.IsCancellationRequested` is false || `token` in closure passed to `OperationCanceledException` != `token` as parameter)
- Any exception is not `OperationCanceledException` was thrown on task creation. A wait on the task created anyway results Faulted.


```cs
var task = Task.Run(() =>
Task task = Task.Factory.StartNew(async () =>
{
await Task.Delay(100);
throw new Exception(); // not an OperationCanceledException // [!code highlight]
});

Expand Down
Loading

0 comments on commit 1115dd9

Please sign in to comment.