C#中有哪些异步操作的办法
目录
在C#中,有几种常见的异步操作方法:
async/await关键字
从C# 5.0开始,C#引入了async和await关键字,用于简化异步编程。通过在方法前加上async关键字并在方法内部使用await,可以让编译器自动管理异步操作和回调。需要注意的是,异步方法通常返回一个Task或Task
//这个示例展示了如何通过异步操作避免阻塞主线程,从而在等待 I/O 操作(如网络下载)时可以执行其他任务。
//同时展示了使用Task.WhenAll等待两个任务都完成才执行下一步操作
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
namespace AsyncAwaitExample
{
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Starting downloads...");
Task<string> downloadTask1 = DownloadFileAsync("https://www.example.com/file1.txt");
Task<string> downloadTask2 = DownloadFileAsync("https://www.example.com/file2.txt");
// 当文件下载时,执行其他任务
PerformOtherTasks();
// 等待所有文件下载完成
string[] fileContents = await Task.WhenAll(downloadTask1, downloadTask2);
Console.WriteLine("All downloads completed.");
Console.WriteLine("File 1 content: ");
Console.WriteLine(fileContents[0]);
Console.WriteLine("File 2 content: ");
Console.WriteLine(fileContents[1]);
}
static async Task<string> DownloadFileAsync(string url)
{
using HttpClient client = new HttpClient();
string content = await client.GetStringAsync(url);
return content;
}
static void PerformOtherTasks()
{
Console.WriteLine("Performing other tasks while files are being downloaded...");
// 执行其他任务的代码
}
}
}
//这个示例展示了如何通过异步操作避免阻塞主线程,从而在等待 I/O 操作时可以执行其他任务。
//同时展示了使用Task.WhenAll等待两个任务都完成才执行下一步操作
using System;
using System.Threading.Tasks;
namespace AsyncAwaitExample
{
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Starting tasks...");
Task<int> task1 = CalculateResultAsync(1, 2);
Task<int> task2 = CalculateResultAsync(3, 4);
// 当等待长耗时IO任务时,执行其他任务
PerformOtherTasks();
// 等待两个任务都完成
int[] results = await Task.WhenAll(task1, task2);
Console.WriteLine($"Results: {results[0]} and {results[1]}");
Console.WriteLine("Both tasks completed. Continuing...");
Console.ReadLine();
}
private static async Task<int> CalculateResultAsync(int a, int b)
{
await Task.Delay(TimeSpan.FromSeconds(2)); // 模拟耗时操作
return a + b;
}
}
}
Task Parallel Library (TPL)
TPL是.NET Framework 4引入的,用于处理并发编程。TPL通过Task类和其他相关类来支持并发操作。可以通过调用Task.Run()方法将操作包装成异步任务。
public Task<int> CalculateResultAsync()
{
return Task.Run(() =>
{
int result = LongRunningCalculation();
return result;
});
}
Event-based Asynchronous Pattern (EAP)
EAP是在.NET Framework中较早引入的异步模式,通常以BeginXXX和EndXXX方法成对出现,并通过事件通知调用者操作已完成。虽然此模式在较新的.NET版本中已经被async/await取代,但仍有一些旧版本库在使用。
//请注意,尽管这个示例没有直接使用BeginXXX和EndXXX方法,但它仍然是基于事件的异步模式。使用WebClient类的旧版异步API(如DownloadFileAsync方法)实际上是基于BeginXXX和EndXXX方法的,但在这里已经被封装起来了。对于不涉及网络操作的示例,可能会更明显地看到BeginXXX和EndXXX方法。但在实际开发中,我们建议使用async/await关键字和Task Parallel Library,这两种方法提供了更好的编程模型。
using System;
using System.ComponentModel;
using System.IO;
using System.Net;
namespace EAPExample
{
class Program
{
static void Main(string[] args)
{
string url = "https://www.example.com/file.txt";
string localPath = "file.txt";
FileDownloader downloader = new FileDownloader();
downloader.DownloadCompleted += Downloader_DownloadCompleted;
downloader.DownloadProgressChanged += Downloader_DownloadProgressChanged;
Console.WriteLine("开始下载...");
downloader.DownloadFileAsync(url, localPath);
Console.ReadLine();
}
private static void Downloader_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
Console.WriteLine($"下载进度: {e.ProgressPercentage}%");
}
private static void Downloader_DownloadCompleted(object sender, AsyncCompletedEventArgs e)
{
Console.WriteLine("下载完成!");
}
}
class FileDownloader
{
private WebClient _client;
public event EventHandler<AsyncCompletedEventArgs> DownloadCompleted;
public event EventHandler<DownloadProgressChangedEventArgs> DownloadProgressChanged;
public void DownloadFileAsync(string url, string localPath)
{
_client = new WebClient();
_client.DownloadFileCompleted += Client_DownloadFileCompleted;
_client.DownloadProgressChanged += Client_DownloadProgressChanged;
_client.DownloadFileAsync(new Uri(url), localPath);
}
private void Client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
DownloadProgressChanged?.Invoke(this, e);
}
private void Client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
DownloadCompleted?.Invoke(this, e);
}
}
}
使用线程
虽然不推荐直接使用线程进行异步操作,但在某些特殊情况下,可能需要手动创建和管理线程。可以使用Thread类或ThreadPool类创建线程。
public void StartNewThread()
{
Thread thread = new Thread(LongRunningMethod);
thread.Start();
}
//线程池可以帮助我们降低线程创建及销毁的开销,和上下文切换的开销。
//需要注意的是,在高并发情况下,ThreadPool可能会遇到线程饥饿问题。
//因此,在执行大量长时间运行的任务时,请确保线程池配置正确,以便为其他并发任务留出足够的线程。
public void StartNewThreadWithThreadPool()
{
ThreadPool.QueueUserWorkItem(state => LongRunningMethod());
}
ThreadPool还有哪些好用的方法
int workerThreads;
int completionPortThreads;
ThreadPool.GetMinThreads(out workerThreads, out completionPortThreads); //获取线程池中工作线程的最小数量
ThreadPool.SetMinThreads(workerThreads + 2, completionPortThreads); //设置线程池中工作线程的最小数量。
ThreadPool.GetMaxThreads(out workerThreads, out completionPortThreads); //获取线程池中工作线程的最大数量
ThreadPool.SetMaxThreads(workerThreads - 2, completionPortThreads); //设置线程池中工作线程的最大数量。
ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads); //获取线程池中可用的工作线程数量。
//UnsafeQueueUserWorkItem将工作项排入队列,但不考虑调用线程的安全上下文。在某些特殊情况下,这可能会导致性能提升,但需要确保工作项不依赖于调用线程的安全上下文。
ThreadPool.UnsafeQueueUserWorkItem(state =>
{
Console.WriteLine("Unsafe work item executing");
}, null);
//RegisterWaitForSingleObject:将等待操作排入线程池。当指定的等待句柄收到信号或超时时,线程池将执行回调方法。这允许你在等待系统资源时不阻塞线程。
AutoResetEvent autoResetEvent = new AutoResetEvent(false);
ThreadPool.RegisterWaitForSingleObject(autoResetEvent, (state, timedOut) =>
{
Console.WriteLine("Wait handle signaled or timeout occurred");
}, null, TimeSpan.FromSeconds(10), true);
/*
ThreadPool
开10个线程,1号到10号,
结果为res,类型是 int []
第1号线程计算【从1加到5】,结果存放到res的0号位置
第2号线程计算【从6加到10】,结果存放到res的1号位置
...
第n号线程计算【从5*n-4加到5*n】,结果存放到res的(n-1)号位置
如果全部线程都计算完了,打印res。
*/
using System;
using System.Threading;
namespace ThreadPoolExample
{
class Program
{
private static int[] res = new int[10];
private static int finishedThreads = 0;
private static object locker = new object();
static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
{
int threadNumber = i + 1;
ThreadPool.QueueUserWorkItem(CalculateSum, threadNumber);
}
while (true)
{
lock (locker)
{
if (finishedThreads == 10)
{
Console.WriteLine("结果数组:");
for (int i = 0; i < res.Length; i++)
{
Console.WriteLine($"res[{i}] = {res[i]}");
}
break;
}
}
Thread.Sleep(100);
}
}
private static void CalculateSum(object state)
{
int threadNumber = (int)state;
int start = 5 * threadNumber - 4;
int end = 5 * threadNumber;
int sum = 0;
for (int i = start; i <= end; i++)
{
sum += i;
}
lock (locker)
{
res[threadNumber - 1] = sum;
finishedThreads++;
}
}
}
}
结论
总之,推荐使用async/await关键字和Task Parallel Library进行异步操作,这两种方法提供了更好的编程模型,易于理解和维护。不过,在使用旧版本库或特殊场景下,可能需要使用Event-based Asynchronous Pattern或手动管理线程。