Description
Permet d'effectuer des tâches en parallèle ou des tâches sans consommer le thread de l'UI.
L'utilisation de Task permet de gagner en réactivité mais pas en performance.
Délégué asynchrone
|
Task taskA = Task.Run(() => Thread.Sleep(4000));
Console.WriteLine("taskA Status: {0}", taskA.Status);
Console.WriteLine("taskA Status: {0}", taskA.Status);
taskA.Wait();
Console.WriteLine("taskA Status: {0}", taskA.Status);
Task<int> t2 = Task.Run(() => 42 );
t2.Result;
|
Enchaînement de tâches
|
Task<int> t = Task.Run(() => 42)
.ContinueWith((antecedent) => antecedent.Result * 2);
Task<int> t = Task.Run(() => 42);
t.ContinueWith((antecedent) =>
{
Console.WriteLine("Canceled");
}, TaskContinuationOptions.OnlyOnCanceled);
t.ContinueWith((antecedent) =>
{
Console.WriteLine("Faulted");
}, TaskContinuationOptions.OnlyOnFaulted);
var completedTask = t.ContinueWith((antecedent) =>
{
Console.WriteLine("Completed");
}, TaskContinuationOptions.OnlyOnRanToCompletion);
completedTask.Wait();
|
TaskFactory et sous-tâches
|
Task<int[]> parent = Task.Run(() =>
{
var results = new int[3];
TaskFactory tf = new TaskFactory(TaskCreationOptions.AttachedToParent,
TaskContinuationOptions.ExecuteSynchronously);
tf.StartNew(() => results[0] = 0);
tf.StartNew(() => results[1] = 1);
tf.StartNew(() => results[2] = 2);
return results;
});
var finalTask = parent.ContinueWith(parentTask =>
{
foreach (int i in parentTask.Result)
Console.WriteLine(i);
});
finalTask.Wait();
Task[] tasks;
Task.WaitAll(tasks);
int index = Task.WaitAny(tasks);
|
Annuler une tâche
|
var cancellationTokenSource = new CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;
Task task = Task.Run(() =>
{
while (!token.IsCancellationRequested)
{
Console.Write("*");
Thread.Sleep(1000);
}
token.ThrowIfCancellationRequested();
}, token);
Console.WriteLine("Press enter to stop the task");
Console.ReadLine();
cancellationTokenSource.Cancel();
try
{
task.Wait();
}
catch (AggregateException e)
{
Console.WriteLine(e.InnerExceptions[0].Message);
}
|
async await
Permet d'écrire facilement du code asynchrone, exemple: ne pas bloquer le thread graphique lors de tâches longues à exécuter.
Alternative élégante au BackgroundWorker
Une méthode async tourne séquentiellement mais permet de faire des appels await sur des blocs de code.
await permet d’exécuter des tâches longues sans bloquer le thread UI.
|
button.Clicked += Button_Clicked;
async void Button_Clicked(object sender, EventArgs e)
{
await DoItAsync();
label.Text = "Traitement en cours !!!";
await Task.Run(() =>
{
System.Threading.Thread.Sleep(10000);
});
label.Text = "Traitement terminé !!!";
}
async Task DoItAsync()
{
label1.Text = "Traitement en cours !!!";
await Task.Run(() =>
{
System.Threading.Thread.Sleep(5000);
});
label1.Text = "Traitement terminé !!!";
}
|
Callback
|
private async Task DoItAsync(string param, Action callback)
{
await MyMethodAsync();
callback();
|
Paramètre out
Les méthodes async ne supportent pas les paramètres out.
Utiliser un tuple comme paramètre de retour à la place.
|
public async Task<(int status, string result)> MyMethod() {}
|
Application Console
|
string result = MyMethodAsync().GetAwaiter().GetResult();
static async Task<string> MyMethodAsync()
{
}
|
Nuget:
- Nito.Mvvm.Async prerelease
- FontAwesome.WPF
MainWindow.xaml
|
<Window xmlns:Controls="clr-namespace:System.Windows.Controls;assembly=PresentationFramework"
xmlns:fa="http://schemas.fontawesome.io/icons/">
<Window.Resources>
<Controls:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<TextBox Text="{Binding Query, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="{Binding ResultTask.Result}" />
<Label Content="Loading..."
Visibility="{Binding ResultTask.IsNotCompleted,
Converter={StaticResource BooleanToVisibilityConverter},
FallbackValue=Collapsed}"/>
<fa:ImageAwesome Icon="Refresh" Spin="True" Height="16" Width="16"
Visibility="{Binding ResultTask.IsNotCompleted,
Converter={StaticResource BooleanToVisibilityConverter},
FallbackValue=Collapsed}" />
<Label Content="{Binding NotifyValuesTask.ErrorMessage}"
Visibility="{Binding ResultTask.IsFaulted,
Converter={StaticResource BooleanToVisibilityConverter}}"/>
|
MainVM.cs
|
private string _query;
public string Query
{
get { return _query; }
set
{
Set(() => Query, ref _query, value, true);
ResultTask = NotifyTask.Create(GetResultAsync(_query, MyCallback));
}
}
private void MyCallback() { }
private NotifyTask<string> _resultTask;
public NotifyTask<string> ResultTask
{
get
{
return _resultTask;
}
set
{
Set(() => ResultTask, ref _resultTask, value, true);
}
}
public async Task<string> GetResultAsync(string query, Action callback)
{
var url = $"http://localhost:57157/api/v1/test/result/{query}";
var responseMessage = await _client.GetAsync(url);
if (responseMessage.IsSuccessStatusCode)
{
return await responseMessage.Content.ReadAsStringAsync();
}
else
{
return await Task.FromResult($"{responseMessage.StatusCode}: {responseMessage.ReasonPhrase}");
}
callback();
}
|
Exceptions
|
catch (AggregateException e)
{
Console.WriteLine("There where {0} exceptions", e.InnerExceptions.Count);
}
|
Accès aux ressources
Concurrent collections
BlockingCollection<T> |
ajout et suppression thread-safe. Add, Take. FIFO par défaut.
|
ConcurrentBag<T> |
sans ordre, doublons autorisés. Add, TryTake, TryPeek.
|
ConcurrentDictionary<TKey,T> |
TryAdd, TryUpdate, AddOrUpdate, GetOrAdd.
|
ConcurrentQueue<T> |
FIFO. Enqueue, TryDequeue.
|
ConcurrentStack<T> |
LIFO. Push, TryPop.
|
|
BlockingCollection<string> col = new BlockingCollection<string>();
col.Add("text");
string s = col.Take();
foreach (string v in col.GetConsumingEnumerable())
Console.WriteLine(v);
|
lock
Permet de synchroniser l'accès aux ressources.
 |
Deadlock si 2 threads s'attendent l'un l'autre. |
|
object _lock = new object();
int n = 0;
var up = Task.Run(() =>
{
for (int i = 0; i < 1_000_000; i++)
{
lock (_lock)
n++;
}
});
for (int i = 0; i < 1_000_000; i++)
{
lock (_lock)
n--;
}
up.Wait();
Console.WriteLine(n);
|
|
public static class MyStaticResource
{
static object _lock = new object();
public static void DoSomething()
{
lock (_lock)
{ ... }
}
}
|
Double-checked locking
Interlocked
Permet de réaliser des opérations atomiques. Évite qu'un autre thread s’insère entre la lecture et l'écriture d'une variable.
|
var up = Task.Run(() =>
{
for (int i = 0; i < 1_000_000; i++)
Interlocked.Increment(ref n);
});
for (int i = 0; i < 1_000_000; i++)
Interlocked.Decrement(ref n);
int oldValue = Interlocked.Exchange(ref value, newValue);
Interlocked.CompareExchange(ref value, newValue, compareTo);
|
Parallel
Utile si le code n'est pas séquentiel.
|
Parallel.For(0, 10, i =>
{
Thread.Sleep(1000);
Console.WriteLine(i);
});
var numbers = Enumerable.Range(10, 20);
Parallel.ForEach(numbers, (int i, ParallelLoopState loopState) =>
{
Thread.Sleep(1000);
Console.WriteLine(i);
loopState.Break();
});
|
PLINQ
|
var numbers = Enumerable.Range(0, 100_000_000);
var parallelResult = numbers.AsParallel().AsOrdered()
.Where(i => i % 2 == 0);
parallelResult.ForAll(e => Console.WriteLine(e));
|
|
public static Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body)
{
return Task.WhenAll(
from partition in Partitioner.Create(source).GetPartitions(dop)
select Task.Run(async delegate
{
using (partition)
while (partition.MoveNext())
await body(partition.Current);
}));
}
await myItems.ForEachAsync(100, async (myItem) =>
{
myItem.MyProperty = await MyMethodAsync();
});
|