在现代软件开发领域,编写异步代码对于确保应用程序的响应性和可扩展性至关重要。C# 和 .NET Core 提供了强大的工具,用于使用 和 关键字管理异步操作。在这篇博客文章中,我们将探讨在 C# 中与 .NET Core 结合使用的最佳实践,从简单的示例开始,逐步转向更复杂的方案。asyncawaitasync/await
在深入研究最佳实践之前,让我们简要了解一下为什么我们在 C# 中使用:async/await
每当遇到 I/O 绑定操作(如数据库查询、网络请求或文件 I/O)时,请考虑使方法 .例如:async
using System;
using System.Net.Http;
using System.Threading.Tasks;
public class AsyncExample
{
public async Task<string> FetchDataAsync()
{
// Simulate an HTTP request
using (var client = new HttpClient())
{
var response = await client.GetAsync("https://jsonplaceholder.typicode.com/posts/1");
return await response.Content.ReadAsStringAsync();
}
}
public static async Task Main(string[] args)
{
var example = new AsyncExample();
var result = await example.FetchDataAsync();
Console.WriteLine(result);
}
}
解释:
using System;
using System.Threading.Tasks;
public class AsyncExample
{
public async Task ProcessDataAsync()
{
// Simulate an async operation
await Task.Delay(1000); // Delay for 1 second
Console.WriteLine("Data processing completed.");
}
public static async Task Main(string[] args)
{
var example = new AsyncExample();
await example.ProcessDataAsync();
}
}
解释:
using System;
using System.Threading.Tasks;
public class AsyncExample
{
public async Task DoSomethingAsync()
{
await Task.Delay(1000).ConfigureAwait(false); // ConfigureAwait(false) for non-UI thread
Console.WriteLine("Operation completed.");
}
public static async Task Main(string[] args)
{
var example = new AsyncExample();
await example.DoSomethingAsync();
}
}
解释:
using System;
using System.Threading.Tasks;
public class AsyncExample
{
// Define an event
public event EventHandler<string> OperationCompleted;
public async Task DoSomethingAsync()
{
await Task.Delay(1000);
Console.WriteLine("Operation completed.");
// Raise the event when the operation is completed
OperationCompleted?.Invoke(this, "Operation completed.");
}
public async void HandleAsyncEvent()
{
await DoSomethingAsync();
}
public static async Task Main(string[] args)
{
var example = new AsyncExample();
// Subscribe to the event
example.OperationCompleted += (sender, message) =>
{
Console.WriteLine("Event handler: " + message);
};
await example.DoSomethingAsync(); // Good practice
example.HandleAsyncEvent(); // Only for asynchronous event handlers
}
}
解释:
通过这个完整的事件处理程序示例,您可以了解如何与事件结合使用。当 中的异步操作完成时,它将引发事件,并且方法中的事件处理程序通过打印消息来对它做出反应。此模式对于异步通知和事件驱动编程非常有用。async/awaitDoSomethingAsyncMain
使用或并发或按顺序执行多个异步操作来编写多个异步操作。Task.WhenAllTask.WhenAny
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
public class AsyncExample
{
public async Task<string> FetchDataAsync()
{
await Task.Delay(2000); // Simulate data fetching
return "Data fetched successfully.";
}
public async Task\<string\> ProcessDataAsync()
{
await Task.Delay(1500); // Simulate data processing
return "Data processed successfully.";
}
public async Task<string> SaveDataAsync()
{
await Task.Delay(1000); // Simulate data saving
return "Data saved successfully.";
}
public async Task ProcessDataConcurrentlyAsync()
{
var tasks = new List<Task<string>> // Use Task\<string> to store results
{
FetchDataAsync(),
ProcessDataAsync(),
SaveDataAsync()
};
// Start all tasks concurrently and wait for all to complete
string[] results = await Task.WhenAll(tasks);
// Process results or perform other operations
foreach (string result in results)
{
Console.WriteLine(result);
}
}
public static async Task Main(string[] args)
{
var example = new AsyncExample();
await example.ProcessDataConcurrentlyAsync();
}
}
解释:
此代码演示了使用 组合异步操作的强大功能。它允许您同时执行多个异步任务,并有效地等待所有任务完成。这在需要并行执行多个异步操作(例如从不同源获取数据、处理数据和同时保存数据)的情况下特别有用。Task.WhenAll
使用块在异步代码中正确处理异常。避免吞下异常;相反,请记录或报告它们。try-catch
using System;
using System.Threading.Tasks;
public class AsyncExample
{
public async Task<int> DivideAsync(int dividend, int divisor)
{
try
{
// Simulate a potentially problematic async operation
await Task.Delay(1000); // Delay for 1 second
if (divisor == 0)
{
throw new DivideByZeroException("Divisor cannot be zero.");
}
return dividend / divisor;
}
catch (DivideByZeroException ex)
{
// Handle specific exception
Console.WriteLine("DivideByZeroException: " + ex.Message);
throw; // Rethrow the exception
}
catch (Exception ex)
{
// Handle general exception
Console.WriteLine("An error occurred: " + ex.Message);
throw; // Rethrow the exception
}
}
public static async Task Main(string[] args)
{
var example = new AsyncExample();
try
{
int result = await example.DivideAsync(10, 2);
Console.WriteLine("Result: " + result);
}
catch (Exception ex)
{
// Handle exceptions here
Console.WriteLine("Exception caught in Main: " + ex.Message);
}
}
}
解释:
在该方法中,我们在块内使用有效参数 ( 和 ) 进行调用。我们在块中处理异常,在块中打印异常消息。MainDivideAsync102trycatch
该代码演示如何处理异步方法中的特定异常和常规异常。正确捕获和处理异常非常重要,无论这些异常是特定于您的操作还是更一般的异常。此外,重新引发异常可确保它们不会被吞噬,从而允许更高级别的代码在必要时处理它们。throw
当您运行此代码时,它将成功执行除法操作,您将看到打印到控制台的“结果”。但是,如果将除数更改为 ,它将引发一个 ,并且您将看到打印到控制台的相应异常处理消息。0DivideByZeroException
当您需要正常取消异步方法时,请将其传递给异步方法。这对于用户发起的取消等情况非常有用。CancellationToken
using System;
using System.Threading;
using System.Threading.Tasks;
public class AsyncExample
{
public async Task DownloadFileAsync(string url, CancellationToken cancellationToken)
{
try
{
Console.WriteLine("Downloading file from " + url);
using (var client = new HttpClient())
{
// Simulate a long-running download
await Task.Delay(1000, cancellationToken); // Delay for 1 second, can be canceled
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Download canceled.");
cancellationToken.ThrowIfCancellationRequested();
}
Console.WriteLine("Download completed.");
}
}
catch (OperationCanceledException ex)
{
Console.WriteLine("OperationCanceledException: " + ex.Message);
}
}
public static async Task Main(string[] args)
{
var example = new AsyncExample();
// Create a CancellationTokenSource to manage the cancellation
using (var cancellationTokenSource = new CancellationTokenSource())
{
// Simulate user-initiated cancellation after 500ms
cancellationTokenSource.CancelAfter(500);
try
{
await example.DownloadFileAsync("https://example.com/bigfile.zip", cancellationTokenSource.Token);
}
catch (Exception ex)
{
Console.WriteLine("Exception caught: " + ex.Message);
}
}
}
}
解释:
此示例演示如何使用正常取消异步操作。它允许您实施用户启动的取消或处理需要停止正在进行的操作而不突然终止应用程序的情况。运行此代码时,您将看到相应的消息,指示下载进度和取消。CancellationToken
在编写可重用的库代码时,请随意使用以防止使用者应用程序中出现潜在的死锁。将同步上下文的选择留给调用方。ConfigureAwait(false)
在 .NET 中,异步代码执行可以与同步上下文相关联。同步上下文确定异步操作的计划和执行方式。在大多数 UI 应用程序中,都有一个同步上下文,可确保异步代码在 UI 线程上运行以更新用户界面组件。
但是,在某些情况下,尤其是在编写库代码时,使用同步上下文可能会导致死锁或性能低下。这是因为阻塞 UI 线程或其他特殊线程可能会导致应用程序性能无响应或下降。为避免这种情况,您可以在库代码中等待异步操作时使用。ConfigureAwait(false)
using System;
using System.Threading.Tasks;
public class LibraryCode
{
public async Task<string> SomeLibraryMethod()
{
// Simulate an asynchronous operation
await Task.Delay(1000).ConfigureAwait(false);
// Return a result
return "Library operation completed.";
}
}
在此示例中,我们有一个名为 的库类,它包含一个名为 的方法。在此方法中:LibraryCodeSomeLibraryMethod
我们使用 执行异步操作。通过使用 ,我们显式指定不希望捕获当前同步上下文。await Task.Delay(1000).ConfigureAwait(false);ConfigureAwait(false)
现在,让我们在消费者应用程序中使用此库:
using System;
using System.Threading.Tasks;
public class ConsumerApp
{
public async Task UseLibraryCode()
{
var library = new LibraryCode();
string result = await library.SomeLibraryMethod();
Console.WriteLine("Result: " + result);
}
}
在消费者应用程序中,我们创建一个实例并异步调用。LibraryCodeSomeLibraryMethod
总之,在库代码中使用是一种很好的做法,可以防止潜在的死锁,并使库在各种应用程序上下文中使用时更加健壮。它将同步上下文的选择权留给调用方,使使用者能够控制他们想要如何处理异步操作,并避免意外的 UI 线程阻塞或性能问题。ConfigureAwait(false)
分析异步代码以识别性能瓶颈。通过将 CPU 密集型操作卸载到单独的线程池来优化这些操作。在此最佳实践中,我们将探讨如何将 CPU 密集型操作卸载到单独的线程池,以防止阻塞主线程。下面是一个完整的代码片段来说明这一点:
using System;
using System.Diagnostics;
using System.Threading.Tasks;
public class AsyncExample
{
public async Task HeavyCpuBoundOperationAsync()
{
var stopwatch = Stopwatch.StartNew();
// Offload a CPU-bound operation to a separate thread pool
await Task.Run(() =>
{
// Simulate a CPU-bound operation
for (int i = 0; i < 1000000; i++)
{
// Perform some heavy computation
Math.Sqrt(i);
}
});
stopwatch.Stop();
Console.WriteLine("CPU-bound operation completed in " + stopwatch.ElapsedMilliseconds + "ms");
}
public static async Task Main(string[] args)
{
var example = new AsyncExample();
Console.WriteLine("Starting CPU-bound operation...");
await example.HeavyCpuBoundOperationAsync();
Console.WriteLine("CPU-bound operation finished.");
// Continue with other asynchronous work or application logic
}
}
解释:
通过使用 将 CPU 绑定操作卸载到单独的线程池,可以防止它阻塞主线程,并使应用程序保持响应。使用秒表分析操作的执行时间有助于识别性能瓶颈。需要注意的是,并非所有操作都应卸载;您应该只对有意义的 CPU 密集型任务执行此操作。此最佳做法可确保应用程序在高效处理资源密集型任务的同时保持响应。Task.Run
在 C# 中将 .NET Core 与 .NET Core 结合使用可以大大提高应用程序的响应能力和可伸缩性。通过遵循这些最佳实践,您可以编写干净、高效且可维护的异步代码。从简单的示例开始,逐步将这些做法合并到更复杂的异步方案中,以确保平稳过渡。祝您编码愉快!async/await