Task的使用注意事项
Task是基于线程池的封装。Task进行异步操作就是从线程池中获取线程进行操作。
基本操作
Task结束后状态
RanToCompletion, Canceled, Faulted
判断一个Task是否成功完成
task == TaskStatus.RanToCompletion
任务调度机制TaskScheduler
ThreadPoolTaskScheduler
Task的默认机制,任务在ThreadPool上执行;如果当前Task上的TaskCreationOptions设置为LongRunning的话,这个task就会委托到Thread中去执行。
private static readonly CustomThreadTaskScheduler _customThreadTaskScheduler = new CustomThreadTaskScheduler(20);
/// <summary>
/// 长时间运行的线程
/// </summary>
/// <param name="action">线程动作</param>
/// <param name="cts"></param>
public static void LongThread(Action action, CancellationTokenSource cts)
{
Disponse(cts);
cts = new CancellationTokenSource();
Task.Factory.StartNew(action, cts.Token, TaskCreationOptions.LongRunning, _customThreadTaskScheduler).ConfigureAwait(false);
}
public static void LongThread(Action<object> action, CancellationTokenSource cts)
{
Disponse(cts);
cts = new CancellationTokenSource();
Task.Factory.StartNew(action, cts, cts.Token, TaskCreationOptions.LongRunning, _customThreadTaskScheduler).ConfigureAwait(false);
}
private static CancellationTokenSource _cts = new CancellationTokenSource();
WSCommFunc.LongThread(() =>
{
while (true)
{
WSCommFunc.TestMethod();
WSCommFunc.ThreadPoolInfo();
}
}, _cts);
SynchronizationContextTaskScheduler
同步上下文的调度器,原理就是把繁重的耗时工作丢给ThreadPool,然后将更新UI的操作丢给 UI线程的队列中,由UIThread来执行。
自定义调度器
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// 自定义线程调度器
/// </summary>
public sealed class CustomThreadTaskScheduler : TaskScheduler
{
private readonly BlockingCollection<Task> _tasks = new BlockingCollection<Task>();
private readonly Thread[] _threads;
protected override IEnumerable<Task> GetScheduledTasks() => _tasks;
protected override void QueueTask(Task task) => _tasks.Add(task);
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) => false;
public CustomThreadTaskScheduler(int threadCount)
{
_threads = new Thread[threadCount];
for (int index = 0; index < threadCount; index++)
{
_threads[index] = new Thread(_ =>
{
while (true)
{
TryExecuteTask(_tasks.Take());
}
});
}
Array.ForEach(_threads, it => it.Start());
}
}
ContinueWith
一个任务完成后发起一个新任务。
有时候使用 Task 的 Delay 之后,如果想要返回主线程,则有
Task.Delay(TimeSpan.FromSeconds(5)).ContinueWith
(
_ => Foo()
// 如果 Foo 不需要操作UIThread,可以注释下面一段代码提高效率
, TaskScheduler.FromCurrentSynchronizationContext()
);
线程池
线程池的默认最小线程数、最大线程数 与硬件配置有关,最小线程数和逻辑处理器一致(一个处理器保留一个空闲线程)。
不要随意更改线程池的工作线程最小数目,线程上下文切换可能更耗时。
int maxWorkerThreads = 0, maxCompletionPortThreads = 0;
ThreadPool.GetMaxThreads(out maxWorkerThreads, out maxCompletionPortThreads);
Console.WriteLine($"线程池中工作线程的最大数目: {maxWorkerThreads}, 线程池中I/O线程的最大数目: {maxCompletionPortThreads}");
int minWorkerThreads = 0, minCompletionPortThreads = 0;
ThreadPool.GetMinThreads(out minWorkerThreads, out minCompletionPortThreads);
Console.WriteLine($"线程池的空闲工作线程的最小数目: {minWorkerThreads}, 线程池的空闲I/O线程的最小数目: {minCompletionPortThreads}");
Console.WriteLine($"此计算机处理器数量: {Environment.ProcessorCount}");
int count = 200;
Console.WriteLine(Environment.NewLine + "设置最小线程数:" + count);
if (!ThreadPool.SetMinThreads(count, minCompletionPortThreads))
{
Console.WriteLine("Failed!!!!");
}
ThreadPool.GetMinThreads(out minWorkerThreads, out minCompletionPortThreads);
Console.WriteLine($"线程池的空闲工作线程的最小数目: {minWorkerThreads}, 线程池的空闲I/O线程的最小数目: {minCompletionPortThreads}");
等待所有任务完成
多任务长时间操作,等待结果
使用 WaitAll 或 Parallel,一般多用于相对独立的任务列表。
若想操作(统计、计算等)任务的结果,可以加锁,防止并发冲突。
WaitAll
// 多任务长时间操作,等待结果
var tasks = new List<Task>();
for (int i = 0; i < 15; i++)
{
int indx = i;
tasks.Add(Task.Run(()=> TaskLongMethod(indx)));
}
Task.WaitAll(tasks.ToArray());
Console.WriteLine("All Task Completed!!");
foreach (var t in tasks)
{
//Console.WriteLine(t.Result);
}
// 长时间操作
private static int TaskLongMethod(int index)
{
Console.WriteLine($"Task({index,2}): starting.......{DateTime.Now} ");
Stopwatch timer = new Stopwatch();
timer.Start();
Thread.Sleep(_rand.Next(1, 10) * 500);
timer.Stop();
Console.WriteLine($"Task({index,2}): end............{DateTime.Now} 耗时:{timer.ElapsedMilliseconds} ms");\
return 0;
}
Parallel
List<int> list = new List<int>(count);
for (int i = 0; i < 15; i++)
{
list.Add(i);
}
Parallel.ForEach(list, itm =>
{
TaskLongMethod(itm);
});
看着Parallel的效果更好,这只是理论效果,实际应用应根据具体业务具体分析,采用不同的方式。
异步长时间操作,等待结果按顺序输出
LongMethodAsync().GetAwaiter().GetResult();
Console.WriteLine("All Task Completed!!");
static async Task LongMethodAsync()
{
var tasks = new List<Task<Demo>>();
for (int i = 0; i < 15; i++)
{
int indx = i;
tasks.Add(MethodAsync(indx));
}
foreach (var item in tasks.IncompletionOrder())
{
var rslt = await item;
Console.WriteLine($"Task({rslt.Number, 2}): {rslt.Text} ms");
}
}
private static async Task<Demo> MethodAsync(int index)
{
Console.WriteLine($"Task({index, 2}): starting.......{DateTime.Now} ");
Stopwatch timer = new Stopwatch();
timer.Start();
Random rand = new Random();
await Task.Delay(rand.Next(1, 20) * 500);
timer.Stop();
Console.WriteLine($"Task({index, 2}): end............{DateTime.Now} 耗时:{timer.ElapsedMilliseconds} ms");
return new Demo { Number = index, Text = timer.ElapsedMilliseconds.ToString() };
}
其中IncompletionOrder方法,可以参考多线程编程-按完成顺序返回结果
有异常即返回
static async Task LongMethodAsync()
{
var arr = new List<int>();
for (int i = 0; i < 15; i++)
{
arr.Add(i);
}
var listTasks = arr.Select(x => MethodAsync(x));
var reslt = await WhenAllOrFirstException(listTasks);
for (int i = 0; i < reslt.Length; i++)
{
PrintThreadId(reslt[i].ToString());
}
}
using System.Collections.Concurrent;
/// <summary>
/// 异常发生时停止等待,返回现有结果
/// </summary>
/// <typeparam name="T">结果类型</typeparam>
/// <param name="tasks">任务集合</param>
/// <returns></returns>
static Task<T[]> WhenAllOrFirstException<T>(IEnumerable<Task<T>> tasks)
{
var inputs = tasks.ToList();
var ce = new CountdownEvent(inputs.Count);
var tcs = new TaskCompletionSource<T[]>();
Action<Task> onCompleted = (Task t) =>
{
if (t.IsFaulted)
{
tcs.TrySetException(t.Exception.InnerExceptions);
}
if (ce.Signal() && !tcs.Task.IsCompleted)
{
tcs.TrySetResult(inputs.Select(s => s.Result).ToArray());
}
};
foreach (var item in inputs)
{
item.ContinueWith(onCompleted);
}
return tcs.Task;
}
交错完成 Interleaving
谁先完成,先处理谁
static async Task LongMethodAsync()
{
var arr = new List<int>();
for (int i = 0; i < 15; i++)
{
arr.Add(i);
}
var listTasks = arr.Select(x => MethodAsync(x)).ToList();
while (listTasks.Count > 0)
{
try
{
var task = await Task.WhenAny(listTasks);
listTasks.Remove(task);
var result = await task;
// TODO
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
WhenAny适合处理少量任务。当处理大量任务集时,每次WhenAny都会向每个任务注册延续O(\(N^{2}\)),造成性能问题。此时可以使用Interleaved。
static async Task LongMethodAsync()
{
var arr = new List<int>();
for (int i = 0; i < 15; i++)
{
arr.Add(i);
}
var listTasks = arr.Select(x => MethodAsync(x)).ToList();
foreach (var item in Interleaved(listTasks))
{
var result = await item;
// TODO
PrintThreadId(result.ToString());
}
}
/// <summary>
/// 交错操作
/// </summary>
/// <typeparam name="T">结果类型</typeparam>
/// <param name="tasks">任务集合</param>
/// <remarks>实时返回处理结果</remarks>
/// <returns></returns>
static IEnumerable<Task<T>> Interleaved<T>(List<Task<T>> tasks)
{
var source = tasks.Select(s => new TaskCompletionSource<T>()).ToList();
int nextIndex = -1;
foreach (var item in tasks)
{
item.ContinueWith(t =>
{
var tcs = source[Interlocked.Increment(ref nextIndex)];
PrintThreadId(t.Status.ToString());
if (t.IsFaulted)
{
tcs.TrySetException(t.Exception.InnerExceptions);
}
else if (t.IsCanceled)
{
tcs.TrySetCanceled();
}
else
{
tcs.TrySetResult(t.Result);
}
}, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}
return source.Select(s => s.Task);
}
遏制/节流 Throttling
限制并发数量。
static async Task LongMethodAsync()
{
const int CONCURRENCY_LEVEL = 10;
int index = 0;
var arr = new List<int>();
for (int i = 0; i < 15; i++)
{
arr.Add(i);
}
var tasks = new List<Task<Demo>>();
while (index < CONCURRENCY_LEVEL && index < arr.Count)
{
tasks.Add(MethodAsync(index));
index++;
}
while (tasks.Count > 0)
{
try
{
var task = await Task.WhenAny(tasks);
tasks.Remove(task);
var result = await task;
// TODO
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
if (index < arr.Count)
{
tasks.Add(MethodAsync(index));
index++;
}
}
}
超时
与 CancellationTokenSource或Task.Delay 结合使用。
LongMethodAsync().ConfigureAwait(false);
Thread.Sleep(1000); // 1
//Thread.Sleep(10000); // 2
_cts?.Cancel();
当是EAP类方法,无法传递CancellationToken,可以如下操作。
static async Task LongMethodAsync()
{
_cts = new CancellationTokenSource();
var task = MethodAsync(100);
await UnitilCompletionOrCancellation(task, _cts.Token);
if (task.IsCompleted)
{
var result = await task;
Console.WriteLine("Task Completed! " + result);
}
else
{
task.ContinueWith(t =>
{
Console.WriteLine(t.Status);
});
}
}
private static async Task UnitilCompletionOrCancellation(Task asyncOp, CancellationToken token)
{
var tcs = new TaskCompletionSource<bool>();
using (token.Register(() => tcs.TrySetResult(true)))
{
await Task.WhenAny(asyncOp, tcs.Task);
}
}
LongMethodAsync().ConfigureAwait(false);
static async Task LongMethodAsync()
{
var task = MethodAsync(100);
if (task == await Task.WhenAny(task, Task.Delay(5000)))
{
var result = await task;
Console.WriteLine("Task Completed! " + result);
}
else
{
task.ContinueWith(t =>
{
Console.WriteLine("Timeout!" + t.Status);
});
}
}
重试 RetryOnFault
耗时超过5s时,方法抛出异常,继续重试,最后重试n次或者返回正确结果
static async Task LongMethodAsync()
{
var result = await RetryOnFault(() => MethodAsync(10), 3);
Console.WriteLine(result);
}
/// <summary>
/// 失败时重试
/// </summary>
/// <typeparam name="T">返回数据类型</typeparam>
/// <param name="func">异步方法</param>
/// <param name="count">重试次数</param>
/// <param name="retryWhen">何时重试</param>
/// <returns></returns>
static async Task<T> RetryOnFault<T>(Func<Task<T>> func, int count, Func<Task> retryWhen = null)
{
for (int i = 0; i < count; i++)
{
try
{
return await func().ConfigureAwait(false);
}
catch (Exception ex)
{
if ( i == count - 1)
{
Console.WriteLine("重试失败!");
}
else
{
Console.WriteLine(ex.Message);
}
}
// null时直接重试
if (retryWhen != null)
{
await retryWhen().ConfigureAwait(false);
}
}
return default(T);
}
在重试前,等待片刻
var result = await RetryOnFault(() => MethodAsync(10), 3, ()=> Task.Delay(1000));
只需要一个 NeedOnlyOne
利用冗余改进操作延迟,提高成功的可能性。
static async Task LongMethodAsync()
{
var result = await NeedOnlyOne(
token => MethodAsync(1, token),
token => MethodAsync(2, token),
token => MethodAsync(3, token)
);
if (result == null)
{
PrintThreadId("NULL!!");
}
else
{
PrintThreadId(result.ToString());
}
}
/// <summary>
/// 只要从其中一个获得响应,立刻取消剩余的请求
/// </summary>
/// <typeparam name="T">返回类型</typeparam>
/// <param name="funcs">委托</param>
/// <returns></returns>
static async Task<T> NeedOnlyOne<T>(params Func<CancellationToken, Task<T>>[] funcs)
{
var length = funcs.Length;
if (length == 0)
{
return default(T);
}
var arr = new CancellationToken[length];
for (int i = 0; i < length; i++)
{
var function = funcs[i];
Expression<Func<CancellationToken, Task<T>>> exp = ct => function(ct);
CancellationToken token = CancellationToken.None;
var variableParameter = Expression.Variable(typeof(CancellationToken), nameof(token));
var inputParameter = exp.Parameters[0];
Expression.Assign(variableParameter, inputParameter);
arr[i] = token;
}
var cts = CancellationTokenSource.CreateLinkedTokenSource(arr);
var tasks = funcs.Select(s => s(cts.Token)).ToArray();
var completed = await Task.WhenAny(tasks).ConfigureAwait(false);
cts.Cancel();
foreach (var task in tasks)
{
var ignored = task.ContinueWith(t =>
{
Console.WriteLine(t.Status);
}, TaskContinuationOptions.OnlyOnFaulted);
}
if (completed.IsFaulted)
{
return default(T);
}
return await completed;
}
static Random _rand = new Random();
private static async Task<Demo> MethodAsync(int index, CancellationToken token)
{
var cost = _rand.Next(1, 20) * 500;
Console.WriteLine($"Task({index,2}): starting.......{DateTime.Now} (Predicate: {cost} ms)");
Stopwatch timer = new Stopwatch();
timer.Start();
try
{
await Task.Delay(cost, token);
Console.WriteLine($"Task({index,2}): end............{DateTime.Now} 耗时:{timer.ElapsedMilliseconds} ms");
}
catch (TaskCanceledException ex)
{
Console.WriteLine($"Task({index,2}): end............{DateTime.Now} 耗时:{timer.ElapsedMilliseconds} ms {ex.Message}");
}
timer.Stop();
if (timer.ElapsedMilliseconds > 3000)
{
throw new Exception("Timeout!!");
}
return new Demo { Number = index, Text = timer.ElapsedMilliseconds.ToString() };
}
任务的异步缓存
AsyncCache<int, Demo> _cache = new AsyncCache<int, Demo>(MethodAsync);
Random random = new Random();
for (int i = 0; i < 15; i++)
{
int index = random.Next(0, 5);
var result = await _cache[index];
PrintThreadId(i + result.ToString());
}
/// <summary>
/// 小型异步缓存
/// </summary>
/// <typeparam name="TKey">任务对应的密钥</typeparam>
/// <typeparam name="TValue">任务结果类型</typeparam>
public class AsyncCache<TKey, TValue>
{
private readonly Func<TKey, Task<TValue>> _factory;
private readonly ConcurrentDictionary<TKey, Lazy<Task<TValue>>> _map;
public AsyncCache(Func<TKey, Task<TValue>> func)
{
if (func == null)
{
throw new ArgumentNullException("func");
}
_factory = func;
_map = new ConcurrentDictionary<TKey, Lazy<Task<TValue>>>();
}
public Task<TValue> this[TKey key]
{
get
{
if (key == null)
{
throw new ArgumentNullException("key");
}
return _map.GetOrAdd(key, value => new Lazy<Task<TValue>>(() => _factory(value))).Value;
}
}
}