Async In C#5.0(async/await)学习笔记

2024-01-08 23:27:11

此文为Async in C#5.0学习笔记

1、在async/await之前的异步

方式一:基于事件的异步Event-based Asynchronous Pattern (EAP).

private void DumpWebPage(Uri uri)
{
    WebClient webClient = new WebClient();
    webClient.DownloadStringCompleted += OnDownloadStringCompleted;
    webClient.DownloadStringAsync(uri);
}
private void OnDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs eventArgs)
{
    m_TextBlock.Text = eventArgs.Result;
}

方式二:基于IAsyncResult接口的异步

private void LookupHostName()
{
    object unrelatedObject = "hello";
    Dns.BeginGetHostAddresses("oreilly.com", OnHostNameResolved, unrelatedObject);
}
private void OnHostNameResolved(IAsyncResult ar)
{
    object unrelatedObject = ar.AsyncState;//上文中的hello字符串
    IPAddress[] addresses = Dns.EndGetHostAddresses(ar);
    // Do something with addresses
}

方式三:回调

void GetHostAddress(string hostName, Action<IPAddress> callback)
{
    //..
}

private void LookupHostName2()
{
    GetHostAddress("oreilly.com", OnHostNameResolved);
}
private void OnHostNameResolved(IPAddress address)
{
    // Do something with address
}

private void LookupHostName3()
{
    GetHostAddress("oreilly.com", address =>
    {
        // Do something with address and aUsefulVariable
    });
}

方式四:使用Task,尤其是Task<T>

private void LookupHostNameByTask()
{
    Task<IPAddress[]> ipAddressesPromise = Dns.GetHostAddressesAsync("oreilly.com");
    ipAddressesPromise.ContinueWith(_ =>
    {
        IPAddress[] ipAddresses = ipAddressesPromise.Result;
        // Do something with address
        Console.WriteLine("In ContinueWith");//后输出
    });
    Console.WriteLine("After ContinueWith");//先输出
}

共同的缺陷:必须将方法分为两部分
乱如麻的递归

private void LookupHostNames(string[] hostNames)
{
    LookUpHostNamesHelper(hostNames, 0);
}
private static void LookUpHostNamesHelper(string[] hostNames, int i)
{
    Task<IPAddress[]> ipAddressesPromise = Dns.GetHostAddressesAsync(hostNames[i]);
    ipAddressesPromise.ContinueWith(_ =>
    {
        IPAddress[] ipAddresses = ipAddressesPromise.Result;
        // Do something with address
        if (i + 1 < hostNames.Length)
        {
            LookUpHostNamesHelper(hostNames, i + 1);
        }
    });
}

2、使用Async

private async void DumpWebPageAsync(string uri)
{
    WebClient webClient = new WebClient();
    string page = await webClient.DownloadStringTaskAsync(uri);
    Console.WriteLine(page);
}

async/await

Task<string> myTask = webClient.DownloadStringTaskAsync(uri);
// Do something here
string page = await myTask;

注意,下面这样操作可能会有隐患,当firstTask有异常时,将不执行await secondTask

Task<string> firstTask = webClient1.DownloadStringTaskAsync("http://oreilly.com");
Task<string> secondTask = webClient2.DownloadStringTaskAsync("http://simple-talk.com");
string firstPage = await firstTask;
string secondPage = await secondTask;

3、Async方法的返回类型

1、void
2、Task
3、Task<T>

4、Async方法具有传递性

private async Task<int> GetPageSizeAsync(string url)
{
    WebClient webClient = new WebClient();
    string page = await webClient.DownloadStringTaskAsync(url);
    return page.Length;
}

private async Task<string> FindLargestWebPage(string[] urls)
{
    string largest = null;
    int largestSize = 0;
    foreach (string url in urls)
    {
        int size = await GetPageSizeAsync(url);
        if (size > largestSize)
        {
            size = largestSize;
            largest = url;
        }
    }
    return largest;
}

5、Async匿名委托和Lambdas表达式

Func<Task<int>> getNumberAsync = async delegate { return 3; };
Func<Task<string>> getWordAsync = async () => "hello";

6、await实际上做了啥?

同时执行下面两个分支
1、从当前行返回跳出函数,执行其他代码
2、等待await异步执行完成后,继续执行当行后面的语句
注意不恰当的大量使用异步,会必正常使用同步慢。

6、哪里不能使用await

1、catch和finally块

/// <summary>
/// 错误的写法
/// </summary>
private async void InvalidMethodAsync()
{
    var webClient = new WebClient();
    string page;
    try
    {
        page = await webClient.DownloadStringTaskAsync("http://oreilly.com");
    }
    catch (WebException)
    {
        page = await webClient.DownloadStringTaskAsync("http://oreillymirror.com");
    }
}
/// <summary>
/// 正确的写法
/// </summary>
private async void InsteadMethodAsync()
{
    bool failed = false;
    var webClient = new WebClient();
    string page;
    try
    {
        page = await webClient.DownloadStringTaskAsync("http://oreilly.com");
    }
    catch (WebException)
    {
        failed = true;
    }
    if (failed)
    {
        page = await webClient.DownloadStringTaskAsync("http://oreillymirror.com");
    }
}

2、lock块

不在lock内使用await

private async void LockAsync()
{
    var sync = new object();
    lock (sync)
    {
        // Prepare for async operation
    }
    int myNum = await Task.Run(() => { Thread.Sleep(5000); return 1; });
    lock (sync)
    {
        // Use result of async operation
    }
}

3、LINQ查询表达式

private async void LINQQueryAsync()
{
    var alexsInts = new List<int>();
    IEnumerable<Task<int>> tasks = alexsInts
                                   .Where(x => x != 9)
                                   .Select(async x => await DoSomthingAsync(x) + await DoSomthingElseAsync(x));
    IEnumerable<int> transformed = await Task.WhenAll(tasks);
}

private async Task<int> DoSomthingAsync(int x)
{
    return await Task.Run(() => { return x * 2; });
}

private async Task<int> DoSomthingElseAsync(int x)
{
    return await Task.Run(() => { return x + 2; });
}

4、Unsafe Code

不能在unsafe代码块中使用await

7、异常捕获

异步内的异常不会直接抛出,可通过Task的IsFaulted判断

private async void ExceptionCaptureAsync()
{
    var task = ThrownExceptionAsync();
    Console.WriteLine($"1 task.IsFaulted={task.IsFaulted}");
    try
    {
        var result=await task;
        Console.WriteLine("Finished");
    }
    catch(Exception ex)
    {
        Console.WriteLine($"2 Exception:{ex.Message}");
    }
    Console.WriteLine("3 Exit ExceptionCaptureAsync");
}

private async Task<int> ThrownExceptionAsync()
{
    throw new Exception("In ThrownExceptionAsync");
}

输出结果:
在这里插入图片描述

8、async方法在需要之前是同步的

一般来说,async方法,在遇到第一个await之前是同步的。
其他情况:

  • 获取task的result
  • 未到达await时已经return
  • 运行了异步代码(但已执行完成)
  • 到达await时,异步方法也已执行完成

9、基于任务的异步模式(Task-Based Asynchronous Pattern,TAP)

同步方法

  • 有或没有参数,尽量避免ref与out参数
  • 有返回类型
  • 正确的方法名
  • 常见或预期的异常是返回类型的一部分,意外异步应该把抛出
    如:
public static IPHostEntry GetHostEntry(string hostNameOrAddress)

异步方法

  • 有或没有参数,不能有ref或out参数
  • 返回类型为Task或Task<T>
  • 命名格式为NameAsync(以Async结尾)
  • 错误的使用应直接抛出,其他异常在Task内处理
public static async Task<IPHostEntry> GetHostEntryAsync(string hostNameOrAddress)

10、使用Task进行计算密集型操作

Task t = Task.Run(() => MyLongComputation(a, b));

await Task.Run(() => MyLongComputation(a, b));
private async Task<int> TaskDemoAsync()
{
    int a = 0, b = 0;
    CancellationToken cancellationToken = new CancellationToken();
    CustomTaskScheduler taskScheduler = new CustomTaskScheduler();

    var t1 = Task.Factory.StartNew(() => { return MyLongComputation(a, b); },
                                    cancellationToken,
                                    TaskCreationOptions.LongRunning,
                                    taskScheduler);
    return await t1;
}

private static int MyLongComputation(int a,int b)
{
    return a + b;
}
//https://www.infoworld.com/article/3063560/how-to-build-your-own-task-scheduler-in-csharp.html
public sealed class CustomTaskScheduler : TaskScheduler, IDisposable
{
    private BlockingCollection<Task> tasksCollection = new BlockingCollection<Task>();
    private readonly Thread mainThread = null;
    public CustomTaskScheduler()
    {
        mainThread = new Thread(new ThreadStart(Execute));
        if (!mainThread.IsAlive)
        {
            mainThread.Start();
        }
    }
    private void Execute()
    {
        foreach (var task in tasksCollection.GetConsumingEnumerable())
        {
            TryExecuteTask(task);
        }
    }
    protected override IEnumerable<Task> GetScheduledTasks()
    {
        return tasksCollection.ToArray();
    }
    protected override void QueueTask(Task task)
    {
        if (task != null)
            tasksCollection.Add(task);
    }
    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        return false;
    }
    private void Dispose(bool disposing)
    {
        if (!disposing) return;
        tasksCollection.CompleteAdding();
        tasksCollection.Dispose();
    }
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

11、创建Puppet Task

//没有PermissionDialog类
//没有async关键词
private Task<bool> GetUserPermission()
{
    // Make a TaskCompletionSource so we can return a puppet Task
    TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
    // Create the dialog ready
    PermissionDialog dialog = new PermissionDialog();
    // When the user is finished with the dialog, complete the Task using SetResult
    dialog.Closed += delegate { tcs.SetResult(dialog.PermissionGranted); };
    // Show the dialog
    dialog.Show();
    // Return the puppet Task, which isn't completed yet
    return tcs.Task;
}

private async void GetUserPermissionAsync()
{
    if(await GetUserPermission())
    {
        //Do something
    }
}

12、旧的异步交互

IAsyncResult BeginGetHostEntry(string hostNameOrAddress,
				AsyncCallback requestCallback,
				object stateObject)
IPHostEntry EndGetHostEntry(IAsyncResult asyncResult)

public static Task<IPHostEntry> GetHostEntryAsync(string hostNameOrAddress)
{
    TaskCompletionSource<IPHostEntry> tcs = new TaskCompletionSource<IPHostEntry>();
    Dns.BeginGetHostEntry(hostNameOrAddress, asyncResult =>
    {
        try
        {
            IPHostEntry result = Dns.EndGetHostEntry(asyncResult);
            tcs.SetResult(result);
        }
        catch (Exception e)
        {
            tcs.SetException(e);
        }
    }, null);
    return tcs.Task;
}

private async void FromAsyncDemo()
{
    string hostNameOrAddress = "www.baidu.com";
    var t = Task<IPHostEntry>.Factory.FromAsync<string>(Dns.BeginGetHostEntry,
            Dns.EndGetHostEntry,
            hostNameOrAddress,
            null);
    await t;
    Console.WriteLine($"Dns:{t.Result.AddressList.FirstOrDefault()}");
}

13、延时(Task.Delay)

一般延时

await Task.Run(() => Thread.Sleep(100));

使用Timer和TaskCompletionSource(直接使用Task.Delay即可)

private static Task Delay(int millis)
{
    TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
    System.Threading.Timer timer = new System.Threading.Timer(_ => tcs.SetResult(null), null, millis, Timeout.Infinite);
    tcs.Task.ContinueWith(delegate { timer.Dispose(); });
    return tcs.Task;
}

14、等待所有Task完成(Task.WhenAll)

private async void TaskWhenAllDemoAsync()
{
    List<Task<string>> tasks = new List<Task<string>>();
    List<string> domains = new List<string>() { "1", "2", "3" };
    var rnd = new Random((int)DateTime.Now.Ticks);
    foreach (string domain in domains)
    {
        tasks.Add(GetStringAsync(domain, rnd.Next(100, 1000)));
    }
    //等价
    //var tasks2 = domains.Select(domain => GetStringAsync(domain, rnd.Next(100, 1000)));

    var allTask = Task.WhenAll(tasks);
    var taskResult = await allTask;
    
    foreach (var str in taskResult)
    {
        Console.WriteLine($"domain:{str}");
    }
}

private async Task<string> GetStringAsync(string domain, int sleepTime)
{
    var task = Task.Run(() =>
    {
        Task.Delay(sleepTime);
        Console.WriteLine($"Delay:{sleepTime}ms in Domain:{domain}");
        return domain;
    });
    return await task;
}

输出结果:
在这里插入图片描述

15、等待Task列表中的任一个完成(Task.WhenAny)

public static Task WhenAny(IEnumerable tasks)

private async void WhenAnyDemoAsync()
{
    var domains = new List<string>() { "1", "2", "3" };
    var rnd = new Random((int)DateTime.Now.Ticks);            
    var tasks = domains.Select(domain => GetStringAsync(domain, rnd.Next(100, 1000))).ToList();
    //注意上面与下面运行的结果差别
    // The IEnumerable from Select is lazy, so evaluate it to start the tasks
    //var tasks = domains.Select(domain => GetStringAsync(domain, rnd.Next(100, 1000)));
    Task<Task<string>> anyTask=Task.WhenAny(tasks);//注意,如果没有ToList(),当前才执行tasks内的GetStringAsync函数
    Task<string> winner=await anyTask;
    string strDomain=await winner;
    Console.WriteLine($"Domain:{strDomain}");
    await Task.WhenAll(tasks);//注意,如果没有ToList,这里的task与上面的是两次结果
    Console.WriteLine($"============================\r\n");
}

16、自定义组合

超时时退出

private async Task<T> WithTimeout<T>(Task<T> task, int time)
{
    Task delayTask = Task.Delay(time);
    Task firstToFinish = await Task.WhenAny(task, delayTask);
    if (firstToFinish == delayTask)
    {
        // The delay finished first - deal with any exception
        _ = task.ContinueWith(HandleException);
        throw new TimeoutException();
    }
    return await task; // If we reach here, the original task already finished
}

private static void HandleException<T>(Task<T> task)
{
    if (task.Exception != null)
    {
        //Log Exception
        //Logging.LogException(task.Exception);
    }
}

17、取消异步操作

CancellationTokenSource cts = new CancellationTokenSource();
cancelButton.Click += delegate { cts.Cancel(); };
int result = await dbCommand.ExecuteNonQueryAsync(cts.Token);

foreach (var x in thingsToProcess)
{
cancellationToken.ThrowIfCancellationRequested();//then IsCanceled will be true
// Process x ...
}

18、异步操作期间返回进度

使用 IPRogress<T>

Task<byte[]> DownloadDataTaskAsync(Uri address,
CancellationToken cancellationToken,
IProgress<DownloadProgressChangedEventArgs> progress)

new Progress<int>(percentage => progressBar.Value = percentage);

private Task<byte[]> DownloadDataTaskAsync(Uri address,
                CancellationToken cancellationToken,
                IProgress<int> progress)
{
    var currPercent = 1;
    progress.Report(currPercent);
    return null;
}

private void ReturnProgress()
{
    var progress= new Progress<int>(percentage => progressBar1.Value = percentage);
    progress.ProgressChanged += Progress_ProgressChanged;
}

19、异步操作的生命周期

private async void btn_GetIcon_Click(object sender, EventArgs e)
{
    Console.Clear();
    Enqueue($"1\tStart in btn_GetIcon_Click");
    var task = GetFavIcon("www.csdn.net");
    Enqueue($"3\tStart await GetFavIcon");
    var ico = await task;
    Enqueue($"6\tFinish await in btn_GetIcon_Click,{ico}");
    while(queue.TryDequeue(out string result))
    {
        Console.WriteLine(result);
    }
    
}

private async Task<string> GetFavIcon(string url)
{
    Enqueue($"2\tIn GetFavIcon function");
    var task = Task.Run(() =>
    {
        Enqueue($"3\tStart to GetfavIcon for {url}");
        var spendTime = rndNum.Next(1000, 1500);
        Task.Delay(spendTime);
        Enqueue($"4\tSpend {spendTime}ms To Getfavicon for {url}");
        return url;
    });
    Enqueue($"3\tStart await in GetFavicon");
    var result = await task;
    Enqueue($"5\tFinish await in GetFavicon");
    return $"favicon:{result}";
}

private void Enqueue(string msg)
{
    queue.Enqueue($"{DateTime.Now:HH:mm:ss.FFFFFFF}\t{msg}");
}

输出结果:
在这里插入图片描述
序号为3的输出顺序可能不一致。

20、选择不使用SynchronizationContext(ConfigureAwait)

没太明白

21、异步方法的Result属性

var result = AlexsMethodAsync().Result;

程序会阻塞等待AlexsMethodAsync执行结束

22、异步的异常

async Task CatcherAsync()
{
    try
    {
        Console.Clear();
        Console.WriteLine("In CatcherAsync");
        var task = Thrower();
        Console.WriteLine("Before await Thrower");
        await task;
        Console.WriteLine("After await Thrower");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Cathc Exception in Catcher:{ex.Message}");
    }
}
async Task Thrower()
{
    await Task.Delay(100);
    throw new Exception("Exception in Thrower");
}

private async void btn_ExceptionDemo_Click(object sender, EventArgs e)
{
    await CatcherAsync();
}

输出结果:
在这里插入图片描述

23、使用.ContinueWith(HandleException)处理void类型异步

public static void ForgetSafely(this Task task)
{
    task.ContinueWith(HandleException);
}

private static void HandleException(Task task)
{
    //do something task.Exception
}

24、AggregateException和WhenAll

Task<Image[]> allTask = Task.WhenAll(tasks);
try
{
    await allTask;
}
catch
{
    foreach (Exception ex in allTask.Exception.InnerExceptions)
    {
        // Do something with exception
    }
}

25、尽量在同步模块时抛出异常

private Task<Image> GetFaviconAsync(string domain)
{
    if (domain == null) throw new ArgumentNullException("domain");
    return GetFaviconAsyncInternal(domain);
}
private async Task<Image> GetFaviconAsyncInternal(string domain)
{
    //...
}

26、异步里,finally不一定执行

await DelayForever()及finally里的代码没有执行

async void AlexsMethod()
{
    try
    {
        Console.WriteLine("Before DelayForever");
        await DelayForever();
        Console.WriteLine("After DelayForever");
    }
    finally
    {
        // Never happens
        Console.WriteLine("finally,After DelayForever");
    }
}
Task DelayForever()
{
    Console.WriteLine("IN DelayForever");
    return new TaskCompletionSource<object>().Task;
}

27、lock块内不能使用await

lock (sync)
{
// Prepare for async operation
}
int myNum = await AlexsMethodAsync();
lock (sync)
{
// Use result of async operation
}

if (DataInvalid())
{
	Data d = await GetNewData();
	// Anything could have happened in the await
	if (DataInvalid())
	{
		SetNewData(d);
	}
}

文章来源:https://blog.csdn.net/TyroneKing/article/details/135210007
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。