概述:Task類是.NET Framework 4中引入的關鍵組件,用于表示異步執(zhí)行的單個操作。它比線程更輕量級,適合快速執(zhí)行小型異步任務。本文介紹了創(chuàng)建、運行任務的方法以及任務取消的機制,包括使用CancellationTokenSource對象取消任務和處理阻塞任務的方法。
Task 類表示通常異步執(zhí)行的單個操作。Task 對象是 .NET Framework 4 中首次引入的基于任務的異步模式的核心組件之一。
任務比線程更輕量級。默認任務計劃程序在線程池內(nèi)運行任務。因此,Task 更適合運行小型、快速的異步操作。如果使用任務運行許多長時間運行的任務,則可能會用完線程池中的所有線程,并且新任務必須等待以前的任務完成執(zhí)行。
創(chuàng)建和運行任務
創(chuàng)建和執(zhí)行任務的最常見方法是使用 Task.Run() 或 TaskFactory.StartNew() 方法。
Task.Run 和 TaskFactory.StartNew 之間的主要區(qū)別在于,可以使用 TaskFactory.StartNew 將參數(shù)傳遞給任務。
將參數(shù)傳遞給任務
public System.Threading.Tasks.Task StartNew (Action<object?> action, object? state);
TaskFactory.StartNew() 的一個重載方法采用兩個參數(shù):第一個是異步執(zhí)行的操作委托,第二個 () 是傳遞到操作委托的參數(shù)。第二個參數(shù)是類型。您可以將任何單個對象作為參數(shù)傳入,并在任務中將其轉換為其原始類型。stateobject
取消任務
Task 類支持通過使用 CancellationTokenSource 對象進行取消。此對象具有 Token 屬性和 Cancel() 方法。以下是任務取消的工作原理:
在主線程中,創(chuàng)建一個 CancellationTokenSource 對象。 將 CancellationTokenSource.Token 屬性傳遞給創(chuàng)建的所有任務。 定期檢查任務中的 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... () 方法創(chuàng)建輪詢情境。
if (result.AsyncWaitHandle.WaitOne(1000))
{
// At this point, asynchronous operation completed
Console.WriteLine("Request processed asyncronously.");
};
WaitOne() 方法中的參數(shù) (1000) 是以毫秒為單位的超時值。返回值 false 表示發(fā)生超時;返回值 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() 方法,它將引發(fā) 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 編輯過