1. Asynchronous Programming: async and await
Asynchronous programming in C# is simplified by the async and await keywords. They allow you to write non-blocking code that performs long-running tasks like I/O operations (e.g., reading files, web requests) without blocking the main thread.
async: Marks a method as asynchronous.await: Waits for an asynchronous task to complete without blocking the thread.
Example: Simple Asynchronous Method with async and await
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
Console.WriteLine("Starting async operation...");
// Call the asynchronous method
string result = await DoWorkAsync();
Console.WriteLine(result); // Output: Work completed!
Console.WriteLine("Async operation finished.");
}
static async Task<string> DoWorkAsync()
{
// Simulate an asynchronous operation
await Task.Delay(2000); // Wait for 2 seconds
return "Work completed!";
}
}
How It Works:
DoWorkAsyncis marked asasync, meaning it contains asynchronous operations.await Task.Delay(2000)simulates a delay (asynchronous operation). Theawaitkeyword tells the compiler to wait for the operation to complete without blocking the main thread.- When
awaitis encountered, control returns to the calling method, allowing other work to be done in the meantime.
2. Caller Information Attributes
Caller Information Attributes allow you to obtain information about the caller of a method, such as the file path, line number, or the member name. This is particularly useful for logging, diagnostics, and debugging.
Caller attributes include:
CallerMemberName: Gets the name of the method or property that called the method.CallerFilePath: Gets the full path of the source file where the method was called.CallerLineNumber: Gets the line number in the source file where the method was called.
These attributes are specified in the method signature using optional parameters.
Example: Caller Information Attributes for Logging
using System;
using System.Runtime.CompilerServices;
class Program
{
static void Main()
{
Log("This is a log message.");
}
static void Log(string message,
[CallerMemberName] string memberName = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0)
{
Console.WriteLine($"Message: {message}");
Console.WriteLine($"Called by: {memberName}");
Console.WriteLine($"File path: {filePath}");
Console.WriteLine($"Line number: {lineNumber}");
}
}
How It Works:
- The
Logmethod receives the caller's information automatically using theCallerMemberName,CallerFilePath, andCallerLineNumberattributes. - When
Logis called fromMain, the output will display the method name (Main), the file path, and the line number whereLogwas called.
3. Improved Exception Handling with Asynchronous Code
Handling exceptions in asynchronous methods follows specific rules. If an asynchronous method throws an exception, it is stored in the Task object and re-thrown when awaited. You should wrap await calls in try-catch blocks to handle exceptions.
Example: Async Exception Handling
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
try
{
// Call an async method that throws an exception
await DoWorkAsync();
}
catch (Exception ex)
{
Console.WriteLine($"Exception caught: {ex.Message}");
}
}
static async Task DoWorkAsync()
{
await Task.Delay(1000); // Simulate work
throw new InvalidOperationException("An error occurred during async operation.");
}
}
How It Works:
- If
DoWorkAsyncthrows an exception, it is propagated through theTaskobject. - The exception is caught in the
try-catchblock that surrounds theawait DoWorkAsync()call.
Additional Notes on Asynchronous Exception Handling
Unobserved Exceptions: If you do not
awaita task and it throws an exception, the exception will be "unobserved" until the task is finalized by the garbage collector. This can lead to unexpected behavior, especially in long-running applications.Task Exception Propagation: If you are not using
awaitbut instead are manually handling the task (e.g., usingContinueWith), you need to manually check for exceptions by accessing theTask.Exceptionproperty.
Example: Handling Multiple Tasks with Task.WhenAll
When running multiple tasks concurrently, you can use Task.WhenAll to wait for all tasks to complete, and handle exceptions across tasks.
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
try
{
Task task1 = Task.Run(() => throw new InvalidOperationException("Task 1 failed."));
Task task2 = Task.Run(() => throw new ArgumentException("Task 2 failed."));
// Wait for all tasks to complete
await Task.WhenAll(task1, task2);
}
catch (Exception ex)
{
Console.WriteLine($"Exception caught: {ex.Message}");
}
}
}
How It Works:
Task.WhenAllwaits for all tasks to complete. If any task fails, it propagates the exception.- The exception is caught and handled in the
try-catchblock.
Summary of Key Points
- Asynchronous Programming with
asyncandawaitallows you to write non-blocking code. Theawaitkeyword suspends the execution of the async method until the awaited task completes. - Caller Information Attributes (
CallerMemberName,CallerFilePath,CallerLineNumber) provide information about the method’s caller, aiding in logging and diagnostics. - Improved Exception Handling in asynchronous programming involves catching exceptions from
asyncmethods usingtry-catchblocks aroundawait. This allows you to handle errors in asynchronous operations effectively.
These concepts make C# a more powerful language for modern application development, enabling easier asynchronous programming, enhanced diagnostics, and robust error handling.