概述:Task类是.NET Framework 4中引入的关键组件,用于表示异步执行的单个操作。它比线程更轻量级,适合快速执行小型异步任务。本文介绍了创建、运行任务的方法以及任务取消的机制,包括使用CancellationTokenSource对象取消任务和处理阻塞任务的方法。
Task 类表示通常异步执行的单个操作。Task 对象是 .NET Framework 4 中首次引入的基于任务的异步模式的核心组件之一。
任务比线程更轻量级。默认任务计划程序在线程池内运行任务。因此,Task 更适合运行小型、快速的异步操作。如果使用任务运行许多长时间运行的任务,则可能会用完线程池中的所有线程,并且新任务必须等待以前的任务完成执行。
创建和运行任务
创建和执行任务的最常见方法是使用 Task.Run() 或 TaskFactory.StartNew() 方法。
Task.Run 和 TaskFactory.StartNew 之间的主要区别在于,可以使用 TaskFactory.StartNew 将参数传递给任务。
将参数传递给任务
public System.Threading.Tasks.Task StartNew (Action<object?> action, object? state);
TaskFactory.StartNew() 的一个重载方法采用两个参数:第一个是异步执行的操作委托,第二个 () 是传递到操作委托的参数。第二个参数是类型。您可以将任何单个对象作为参数传入,并在任务中将其转换为其原始类型。stateobject
取消任务
Task 类支持通过使用 CancellationTokenSource 对象进行取消。此对象具有 Token 属性和 Cancel() 方法。以下是任务取消的工作原理:
在主线程中,创建一个 CancellationTokenSource 对象。 将 CancellationTokenSource.Token 属性传递给创建的所有任务。 定期检查任务中的 CancellationToken.IsCancellationRequested 属性,并相应地处理取消。 在主线程中,调用 CancellationTokenSource.Cancel() 方法。Cancel() 方法会将 CancellationToken.IsCancellationRequested 属性设置为 true。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
Task.Run(() =>
{
int count = 1;
while (!token.IsCancellationRequested)
{
Thread.Sleep(1000);
Console.WriteLine($"Sleep {count++} seconds");
}
Console.WriteLine($"Cancellation requested.");
}, token);
var key = Console.ReadKey();
while (key.KeyChar != 'c')
{
key = Console.ReadKey();
}
tokenSource.Cancel();
Console.WriteLine($"Send Cancel command and exit.");
Console.ReadKey();
}
}
}
取消阻止任务
如您所见,上述取消机制依赖于定期检查 CancellationToken.IsCancellationRequested 属性。如果我的任务是阻塞方法调用怎么办?遗憾的是,没有适用于所有方案的通用解决方案。
如果方法支持 Begin/End 异步 API
如果要调用的阻止方法还提供异步替代方法,则可以利用从 Begin... () 方法创建轮询情境。
if (result.AsyncWaitHandle.WaitOne(1000))
{
// At this point, asynchronous operation completed
Console.WriteLine("Request processed asyncronously.");
};
WaitOne() 方法中的参数 (1000) 是以毫秒为单位的超时值。返回值 false 表示发生超时;返回值 true 表示异步操作完成。
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp2
{
internal class Program
{
private static readonly HttpListener listener = new HttpListener { Prefixes = { $"http://localhost:8098/" } };
static void Main(string[] args)
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
Task.Run(() =>
{
listener.Start();
int count = 1;
while (!token.IsCancellationRequested)
{
var result = listener.BeginGetContext(new AsyncCallback(ListenerCallback), listener);
Console.WriteLine($"{count++}: Waiting for request to be processed asyncronously.");
if (result.AsyncWaitHandle.WaitOne(1000))
{
Console.WriteLine("Request processed asyncronously.");
};
}
listener.Close();
}, token);
var key = Console.ReadKey();
while (key.KeyChar != 'c')
{
key = Console.ReadKey();
}
tokenSource.Cancel();
Console.WriteLine($"Send Cancel command and exit.");
Console.ReadKey();
}
private static void ListenerCallback(IAsyncResult result)
{
HttpListener listener = (HttpListener)result.AsyncState;
if (listener.IsListening)
{
HttpListenerContext context = listener.EndGetContext(result);
HttpListenerResponse response = context.Response;
string responseString = "<HTML><BODY> Hello world!</BODY></HTML>";
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
System.IO.Stream output = response.OutputStream;
output.Write(buffer, 0, buffer.Length);
output.Close();
}
}
}
}
如果该方法可以被其他操作中断
这更具体地适用于您调用的方法。有些方法可能支持这种情况,有些方法不支持。让我们以 HttpListener 为例。HttpListener 具有 GetContextAsync() 方法。此方法将阻止任务。但是,如果调用 HttpListener.Stop() 方法,它将引发 HttpListenerException,从而取消阻止 GetContextAsync() 方法。
当应用程序被 GetContextAsync() 方法阻止时,您将如何调用 HttpListener.Stop() 方法?您可以使用 CancellationToken.Register() 方法注册一个委托,该委托将在取消此 CancellationToken 时调用。
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp3
{
internal class Program
{
private static readonly HttpListener listener = new HttpListener { Prefixes = { $"http://localhost:8098/" } };
static void Main(string[] args)
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
token.Register(() =>
{
if (listener.IsListening)
{
listener.Stop();
}
});
Task.Run(async () =>
{
listener.Start();
while (!token.IsCancellationRequested)
{
try
{
Console.WriteLine("Waiting for request to come...");
var context = await listener.GetContextAsync();
SendResponse(context);
Console.WriteLine("Request processed asyncronously.");
}
catch (Exception e)
{
if (e is HttpListenerException)
{
//this gets thrown when the listener is stopped
Console.WriteLine($"Task received cancel request and exit.");
return;
}
Console.WriteLine(e.Message);
}
}
listener.Close();
}, token);
var key = Console.ReadKey();
while (key.KeyChar != 'c')
{
key = Console.ReadKey();
}
tokenSource.Cancel();
Console.WriteLine($"Main thread sent Cancel command and exit.");
Console.ReadKey();
}
private static void SendResponse(HttpListenerContext context)
{
Console.WriteLine($"Send hello word response.");
HttpListenerResponse response = context.Response;
string responseString = "<HTML><BODY> Hello world!</BODY></HTML>";
byte[] buffer = System.Text.Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
System.IO.Stream output = response.OutputStream;
output.Write(buffer, 0, buffer.Length);
output.Close();
}
}
}
该文章在 2024/2/21 12:17:13 编辑过