大家好,本次继续分享自己的学习经历。本文主要分享异步编程中Task的使用,如果能帮助大家希望多多关注文章末尾的微信公众号和知乎三连。各位举手之劳是对我更新技术文章最大的支持。
Thread线程是用来创建并发的一种低级别工具,它具有一些限制,尤其是:
Task类可以很好的解决上述问题,它是一个高级抽象:它代表了一个并发操作(concurrent),该操作可能有Thread支持,或不由Thread支持。
开始一个Task最简单的办法就是使用Task.Run(.net4.5,4.0的时候是Task.Factory.StartNew)传入一个Action委托即可(例子task)
Task.Run(()=>{ Console.WriteLine("do it"); });
Task.Status枚举状态如下这里就不详细分析可以去官方文档查阅具体用法:
public enum TaskStatus
{
//
// 摘要:
// The task has been initialized but has not yet been scheduled.
Created = 0,
//
// 摘要:
// The task is waiting to be activated and scheduled internally by the .NET Framework
// infrastructure.
WaitingForActivation = 1,
//
// 摘要:
// The task has been scheduled for execution but has not yet begun executing.
WaitingToRun = 2,
//
// 摘要:
// The task is running but has not yet completed.
Running = 3,
//
// 摘要:
// The task has finished executing and is implicitly waiting for attached child
// tasks to complete.
WaitingForChildrenToComplete = 4,
//
// 摘要:
// The task completed execution successfully.
RanToCompletion = 5,
//
// 摘要:
// The task acknowledged cancellation by throwing an OperationCanceledException
// with its own CancellationToken while the token was in signaled state, or the
// task's CancellationToken was already signaled before the task started executing.
// For more information, see Task Cancellation.
Canceled = 6,
//
// 摘要:
// The task completed due to an unhandled exception.
Faulted = 7
}
if (task.Status == TaskStatus.RanToCompletion)
{
//当当前线程状态表示完成时则执行后续操作
Console.WriteLine("do it");
}
调用task的wait方法会进行阻塞直到操作完成,相当于thread上的join方法。
Task mytask = Task.Run(()=>
{
Thread.Sleep(3000);
Console.WriteLine("do it");
});
Console.WriteLine(mytask.IsCanceled);//false
mytask.Wait();//阻塞主线程直到mytask执行完毕
Console.WriteLine(mytask.IsCanceled);//true
wait也可以让你指定一个超时时间和一个取消令牌来提前结束等待。
默认情况,CLR在线程池中运行Task,这非常适合短时间运行的Compute-Bound类工作。
针对长时间允许的任务或阻塞操作,你可以不用采用线程池
Task task = Task.Factory.StartNew(()=>
{
Thread.Sleep(3000);
Console.WriteLine("do it");
},TaskCreationOptions.LongRunning);
如果同时运行多个long-running tasks(尤其是其中有处于阻塞状态的),那么性能将会受到很大影响,这是有比TaskCreationOptions.LongRunning更好的办法:
Task有一个泛型子类叫做Task,它允许一个返回值。
使用Func委托或兼容的Lambda表达式来调用Task.Run就可以得到Task。
随后,可以通过Result属性来获得返回的结果。
Task<int> task = Task.Run(()=> {
Console.WriteLine("do it");
return
666;
});
int result = task.Result;
Console.WriteLine(result);
Task可以看做是一个所谓的“未来/许诺”(future、promise),在它里面包裹着一个Result,在稍后的时候就会变得可用。
在CTP版本的时候,Task实际上叫做Future
与Thread不一样,Task可以很方便的传播异常 如果你的task里面抛出了一个未处理的异常,那么该异常就会重新被抛出给:
代码如下:
Task mytask = Task.Run(()=> { throw null; });
try
{
mytask.Wait();
}
catch (AggregateException aex)
{
if (aex.InnerExceptions is NullReferenceException)
{
Console.WriteLine("null");
}
else
{
throw;
}
}
CLR将异常包裹在AggregateException里,以便在并行编程场景中发挥很好的作用。
如果我们不想抛出异常就想知道task有没有发生故障,无需重新抛出异常,通过Task的IsFaulted和IsCanceled属性也可以检测出Task是否发生了故障:
代码如下:
Task<int> mytask = Task.Run(() =>
{
Console.WriteLine("do it");
return 666;
});
var awaiter = mytask.GetAwaiter();
awaiter.OnCompleted(()=>
{
int result = awaiter.GetResult();
Console.WriteLine(result);
});
如果同步上下文出现了,那么OnCompleted会自动捕获它,并将Continuation提交到这个上下文中。这一点在富客户端应用中非常有用,因为它会把Continuation放回到UI线程中。
如果是编写一个库,则不希望出现上述行为,因为开销较大的UI线程切换应该再程序运行离开库的时候只发生一次,而不是出现在方法调用之间。所以,我们可以使用ConfigureAwait方法来避免这种行为
Task<int> mytask = Task.Run(() =>
{
Console.WriteLine("do it");
return 666;
});
var awaiter = mytask.ConfigureAwait(false).GetAwaiter();
awaiter.OnCompleted(()=>
{
int result = awaiter.GetResult();
Console.WriteLine(result);
});
如果没有同步上下文出现,或者你使用的是ConfigureAwait(false),那么Continuation会运行在先前的task的同一个线程上,从而避免不必要的开销。
另外一种附加Continuation的方式就是调用task的Continuewith方法。
Task<int> mytask = Task.Run(() =>
{
Console.WriteLine("do it");
return 666;
});
mytask.ContinueWith(task=>
{
int result = task.Result;
Console.WriteLine(result);
});
Continuewith本身返回一个task,它可以用它来附加更多的Continuation。
但是,必须直接处理AggregateException:
方法源码如下:
public class TaskCompletionSource<TResult>
{
public TaskCompletionSource();
public TaskCompletionSource(object? state);
public TaskCompletionSource(TaskCreationOptions creationOptions);
public TaskCompletionSource(object? state, TaskCreationOptions creationOptions);
public Task<TResult> Task { get; }
public void SetCanceled();
public void SetException(IEnumerable<Exception> exceptions);
public void SetException(Exception exception);
public void SetResult(TResult result);
public bool TrySetCanceled();
public bool TrySetCanceled(CancellationToken cancellationToken);
public bool TrySetException(IEnumerable<Exception> exceptions);
public bool TrySetException(Exception exception);
public bool TrySetResult(TResult result);
}
使用示例代码:
/*
*CODE1
*/
var tcs = new TaskCompletionSource<int>();
new Thread(() =>
{
Thread.Sleep(5000);
tcs.SetResult(42);
})
{
IsBackground = true
}.Start();
Task<int> task = tcs.Task;
Console.WriteLine(task.Result);
/*CODE2
* 调用此方法相当于调用Task.Factory.StartNew
* 并使用TaskCreationOptions.LongRunning选项来创建非线程池的线程
*/
Task<TResult> Run<TResult>(Func<TResult> func)
{
var tcs = new TaskCompletionSource<TResult>();
new Thread(() =>
{
try
{
tcs.SetResult(func());
}
catch (Exception ex)
{
tcs.SetException(ex);
}
})
{
IsBackground = true
}.Start();
return tcs.Task;
}
示例代码:
static void Main(string[] args)
{
//5秒钟之后,Continuation开始的时候,才占用线程
Delay(5000).GetAwaiter().OnCompleted(() => Console.WriteLine(42));
Console.ReadKey();
}
static Task Delay(int milliseconds)
{
var tcs = new TaskCompletionSource<object>();
var timer = new System.Timers.Timer(milliseconds) { AutoReset = false };
timer.Elapsed += delegate { timer.Dispose(); tcs.SetResult(null); };
timer.Start();
return tcs.Task;
}