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;
        }
    }
}
posted @ 2020-08-11 20:36  wesson2019  阅读(368)  评论(0编辑  收藏  举报