1. 异步方法用async关键字修饰的方法
  2. 异步方法的返回值一般是Task<T>,T是真是的返回值类型,如Task<int>
  3. 自定义的异步方法命名时一般用Async结尾,便于后续维护时一眼就可以看出来。一些系统自带的异步函数也是一Async结尾的。如httpclient.GetStringAsync
  4. 异步方法具有传染性,即一个方法内使用了await,则这个方法也必须用async修饰,即若这个方法是有返回值的则加async Task<T>修饰,没有返回值则写async Task修饰
  1. 下面是方法体内写了await,函数没加async修饰的,所以会报错
  2. 正确写法:
staticasyncstringstring filename)

{

usingnew HttpClient())

    {

string> t =  httpclient.GetStringAsync(url);

stringawait t;

    }

}

  1. 下面由于Main中调用了await且无返回值,所以也需要加async Task修饰
1. staticasyncstring[] args)

{

intawait"https://www.baidu.com/", "1.txt");

   Console.WriteLine(leng);

    Console.ReadKey();

}

staticasyncint> DownLoadHtmlAsync(stringstring filename)

{

usingnew HttpClient())

    {

string> t =  httpclient.GetStringAsync(url);

stringawait t;

      File.WriteAllText(filename, html);

return html.Length;

    }

}
  1. 加了Await后,程序会等待线程结束,但是界面不会卡主
  2. 假如一些函数中它调用了await,但是它不支持async修饰,或者不想要async修饰,可以修改成一下(尽量不要这样写,有死锁风险,加了这个,程序运行到.Result时会阻塞线程,一直等待到结果):
  1. 有返回值
1. staticvoidstring[] args)//不加async修饰

{

int> t=DownLoadHtmlAsync("https://www.baidu.com/", "1.txt");

int leng = t.Result;

   Console.WriteLine(leng);

   Console.ReadKey();

}

staticasyncint> DownLoadHtmlAsync(stringstring filename)

{

usingnew HttpClient())

    {

string> t =  httpclient.GetStringAsync(url);

stringawait t;

      File.WriteAllText(filename, html);

return html.Length;

    }
}
  1. 没返回值
staticvoidstring[] args)//不加async修饰

{

"https://www.baidu.com/", "1.txt");

   t.Wait(); 

   Console.ReadKey();

}

staticasyncstringstring filename)

{

usingnew HttpClient())

    {

string> t =  httpclient.GetStringAsync(url);

stringawait t;

      File.WriteAllText(filename, html); 

    }

}
  1. 在winform程序中也可以调用,不过button1_Click下不需要把void改成Task,因为要响应事件
privateasyncvoidobject sender, EventArgs e)

{

intawait"https://www.baidu.com/", "1.txt");

}
  1. 带返回值委托调用异步:
1. Func<int, Task<string>> DD2 = asyncdelegateint g)

{

"hh" + g);

await"https://www.baidu.com/", "1.txt");

return"hh" + g;

};//指向 

Task<string> t = DD2(4);

string ret = t.Result;
  1. 不带返回值委托调用异步:
Action<int> DD2 = asyncdelegateint g)

{

await"https://www.baidu.com/", "1.txt");

};//指向 

DD2(4);//调用

二、原理

用ILspy反编译可以看到(要用低版本的.NET),可以反编译后,其实系统帮我们把这个await里的代码分成了若干块,通过switch(num)来执行每块内容,全部执行完后返回

Futures返回异步结果 异步方法返回值_html

三、自定义异步函数

上述的GetStringAsync都是系统自带的异步函数,实际应用中可以使用Task.Run来自定义异步函数

  1. 无传入参数,无传出参数
1. privateasyncvoidobject sender, EventArgs e)

{

await WriteAllText( ) ;

}

publicasync Task WriteAllText()

{

string"";

await Task.Run(() =>

    {

forint i = 0; i < 10000; i++)

        {

"iiiiiiiiiiii";

        }

"1.txt");

    });
}
  1. 有传入参数,有传出参数
1. privateasyncvoidobject sender, EventArgs e)

{

this.Text=await WriteAllText(900000) ;

}

publicasyncstring> WriteAllText(int Count)

{

string> t =Task.Run(( ) => mWriteText(Count));

stringawait t;

return html;

}


 
publicstringint mCount)

{

string"";

forint i = 0; i < mCount; i++)

        {

"iiiiiiiiiiii";

        }

"1.txt",S);

return"333";
}

    注上述的也可以这样写,并且建议这样写。可以去掉async变得更加简洁

publicstring> WriteAllText(int Count)

{

return Task.Run(() => mWriteText(Count));

//Task<string> t =Task.Run(( ) => mWriteText(Count));

//string html = await t;

//return html;

}

四、Task的回调函数

回调函数即等待线程完成后,再执行的函数。其实Task.Run中不想线程Thread函数专门有个回调函数,可以使用ContinueWith方法来实现

privatevoidobject sender, EventArgs e)

{

$"hh1--{Thread.CurrentThread.ManagedThreadId}");

    Task task = Task.Run(() =>

    {

$"hh2--{Thread.CurrentThread.ManagedThreadId}");

        Thread.Sleep(2000);

$"hh3--{Thread.CurrentThread.ManagedThreadId}");

    });

    task.ContinueWith((t) =>

    {

$"hh4--{Thread.CurrentThread.ManagedThreadId}");

    });

$"hh5--{Thread.CurrentThread.ManagedThreadId}");

}

结果及其解析:

hh1--1
				
hh2—3 //已经启动新线程,线程一进去就执行hh2,新线程是3
					
hh5—1 //由于是Task.Run启用的线程,所以不等待直接执行hh5,hh5是在按钮下不在线程内,所以线程还是1
					
hh3—3 //线程等待了2秒,在新线程里执行hh3, 新线程还是3
					
hh4—4 //线程完成后执行"回调"hh4,hh4有可能是新线程,也可能是按钮下线程。随机性。

上面代码与下面是等价的:

privatevoidobject sender, EventArgs e)

{

$"hh1--{Thread.CurrentThread.ManagedThreadId}");

//这里会警告,因为调用async函数没写await。不过不影响执行
				
$"hh6--{Thread.CurrentThread.ManagedThreadId}");

}

publicasync Task ReturnTask()

{

$"hh2--{Thread.CurrentThread.ManagedThreadId}");

    Task task = Task.Run(() =>

    {

$"hh3--{Thread.CurrentThread.ManagedThreadId}");

        Thread.Sleep(2000);

$"hh4--{Thread.CurrentThread.ManagedThreadId}");

    });

await task;

$"hh5--{Thread.CurrentThread.ManagedThreadId}");

}

结果及其解析:

hh1--1 //程序刚从按钮进入,按钮下线程是1
				
hh2--1 //进入函数执行hh2,线程仍然是1
				
hh3--3 //已经启动新线程,线程一进去就执行hh3,新线程是3
				
hh6--1 //遇到await,程序会把await后面的代码包装成一个回调委托,待线程执行完再执行,然后直接返回函数,执行hh6,线程是按钮下线程是1
				
hh4--3 //线程等待了2秒,在新线程里执行hh4, 新线程还是3
				
hh5--1 //线程完成后执行await后面的代码类似"回调"hh5,hh4有可能是新线程,也可能是按钮下线程。随机性。

 

扩展例子

privatevoidobject sender, EventArgs e)

{

$"hh1--{Thread.CurrentThread.ManagedThreadId}");

    ReturnTask();

$"hh6--{Thread.CurrentThread.ManagedThreadId}");

}

publicasync Task ReturnTask()

{

$"hh2--{Thread.CurrentThread.ManagedThreadId}");

    Task task = Task.Run(() =>

    {

$"hh3--{Thread.CurrentThread.ManagedThreadId}");

        Thread.Sleep(2000);

$"hh4--{Thread.CurrentThread.ManagedThreadId}");

    });

await
						
$"hh5--{Thread.CurrentThread.ManagedThreadId}");

    Task task2 = Task.Run(() =>

    {

$"hh7--{Thread.CurrentThread.ManagedThreadId}");

        Thread.Sleep(2000);

$"hh8--{Thread.CurrentThread.ManagedThreadId}");

}); 

await
					
$"hh9--{Thread.CurrentThread.ManagedThreadId}");

}

结果:

hh8--3

hh9--1

hh1--1

hh2--1

hh3--3

hh6--1

hh4--3

hh5--1

hh7--4

hh8--4

hh9—1

 

规律:在一个函数中有多个await时,它是从上往下执行。即用同步的方法写异步

四、任务超时取消任务

CancellationTokenSource:假如执行时间过长,可以设置取消任务

例子:在任务循环中通过代码判断:

privateasyncvoidobject sender, EventArgs e)

{

new CancellationTokenSource();

    cts.CancelAfter(5000);

this.Text=await WriteAllText(40000, cts.Token); 

}

publicstring> WriteAllText(int Count,CancellationToken mCancellationToken)

{

return Task.Run(() => mWriteText(Count, mCancellationToken) );

//Task<string> t =Task.Run(( ) => mWriteText(Count));

//string html = await t;

//return html;

}


 
publicstringint mCount, CancellationToken mCancellationToken)

{

string"";

forint i = 0; i < mCount; i++)

     {

"iiiiiiiiiiii";

if (mCancellationToken.IsCancellationRequested)

        { 

return"任务超时,已被取消" ;

        }

     }

"1.txt",S);

return"HH:mm:ss");

}

也可以手动取消任务

CancellationTokenSource cts = new CancellationTokenSource();

privatevoidobject sender, EventArgs e)

{

    cts.Cancel();

}

privateasyncvoidobject sender, EventArgs e)

{

    cts.CancelAfter(5000);

this.Text = await WriteAllText(40000, cts.Token);

}

五、多任务等待

1、WhenAny:集合中任何一个任务完成Task就完成

2、WhenAll:所有任务完成Tsak才完成,用于等待多任务都执行结束,不在乎它们的执行顺序

例:

privateasyncvoidobject sender, EventArgs e)

{

    cts.CancelAfter(50000);

string> t1 = WriteAllText(10000, cts.Token);

string> t2 = WriteAllText(20000, cts.Token);

string> t3 = WriteAllText(30000, cts.Token);


 
string[] rets = await Task.WhenAll(t1, t2, t3);

string rets1 = rets[0];

string rets2 = rets[1];

string rets3 = rets[2];

this.Text = rets1 + ",""," + rets3;

}
publicstring> WriteAllText(int Count, CancellationToken mCancellationToken)

{

return Task.Run(() => mWriteText(Count, mCancellationToken));

//Task<string> t =Task.Run(( ) => mWriteText(Count));

//string html = await t;

//return html;

}


 
publicstringint mCount, CancellationToken mCancellationToken)

{

string"";

forint i = 0; i < mCount; i++)

    {

"iiiiiiiiiiii";

if (mCancellationToken.IsCancellationRequested)

        {

return"任务超时,已被取消";

        }

    }

"1.txt", S);

return"HH:mm:ss");

}

注:WhenAll也可以接受一个IEnumerable数组形式的Task<T>等多种函数。可能会用到yield关键字来构建IEnumerable数组。即以下两种方式实现效果是一样的

方式一:

staticstring> Test1()

{

string> list = newstring>();

"hh1");

"hh2");

"hh3");

return list;

}

方式二:

staticstring> Test2()

{

yieldreturn"hh1";

yieldreturn"hh2";

yieldreturn"hh3"; 

}

六、多线程安全

线程安全问题是,一段代码,单线程顺序执行的时候是完全没问题的,但是一放到多线程里就会得到意象不到的结果。一般在多线程同时修改一个对象时

测试例子1

1、多线程假如不加Await,它是不会等待的,如下,输出i结果所有都是5,因为i变量只有一个,从而系统值开辟了

    一个内存空间给i,程序是先把for循环执行完,才执行线程内的代码。所以i一直是5

privatevoidobject sender, EventArgs e)

{

forint i = 0; i < 5; i++)

    {

        Task.Run(() =>

        {

$"{i}Star");

            Thread.Sleep(2000);

$"{i}End");

        });

    }

}
  1. 假如要修改上面输出i结果,使得每次输出跟着循环变量变化而变化,可以这样改进,让系统每次循环都开辟一个独自的内存空间:
1. privatevoidobject sender, EventArgs e)

{

forint i = 0; i < 5; i++)

    {

int k = i;

        Task.Run(() =>

        {

$"{k}Star");

            Thread.Sleep(2000);

$"{k}End");

        });

    }

}

测试例子2

如下代码,在线程中对数组进行添加10000次,待全部完成后,数组的个数会少于10000个。这是由于数组是一个连续的内存,多线程在并发时,可能对同一内存被不同的线程写了两次。

voidobject sender, EventArgs e)

{

int> intList = newint>();

for(int i = 0; i < 10000; i++)

    {

        Task.Run(() =>

        {

                intList.Add(i); 

         }); 

     }

}
  1. 解决办法:Lock单线程化,每次线程进来都对Lock块作用域内进行加锁,其他线程在外面等待。待执行完作用域块完后,系统会自动释放说,其他线程才能进来。
1. //保证方法块只有一个线程运行,其他线程在外面等待

privatestaticreadonlyobjectnewobject();

privatevoidobject sender, EventArgs e)

{

int> intList = newint>();

for(int i = 0; i < 10000; i++)

    {

        Task.Run(() =>

        {

lock (hhLock)

            {

                intList.Add(i);

            } 

         }); 

     }

}
  1. 共用Lock,如下两个For循环共用一个Lock对象,这样程序会先执行完第一个For的所有线程,再执行第二个for的所有线程
privatevoidobject sender, EventArgs e)

{

forint i = 0; i < 2; i++)

        {

int k = i;

            Task.Run(() =>

            {

lock (hhLock)

                  {

$"{k}Star1");

                      Thread.Sleep(2000);

$"{k}End1");

                  }

            }); 

        }

forint i = 0; i < 2; i++)

       {

int k = i;

        Task.Run(() =>

            {

lock (hhLock)

                {

$"{k}Star2");

                    Thread.Sleep(2000);

$"{k}End2");

                }

            });

        }

}
  1. 结果是:
0Star1

0End1

1Star1

1End1

0Star2

0End2

1Star2

1End2
  1. 假如想第一个For与第二个For并发的话,可以使用不同的Lock对象
privatestaticreadonlyobjectnewobject();

privatestaticreadonlyobjectnewobject();

privatevoidobject sender, EventArgs e)

{

forint i = 0; i < 2; i++)

        {

int k = i;

            Task.Run(() =>

            {

lock (hhLock)

                  {

$"{k}Star1");

                      Thread.Sleep(2000);

$"{k}End1");

                  }

            }); 

        }

forint i = 0; i < 2; i++)

       {

int k = i;

        Task.Run(() =>

            {

lock (hhLock2)

                {

$"{k}Star2");

                    Thread.Sleep(2000);

$"{k}End2");

                }

            });

        }

}
  1. 结果是:
1. 
1Star1

0Star2

1End1

0Star1

0End2

1Star2

0End1

1End2
  1. 泛型类时,如下,Show(1)与Show(2)时不可以并发的,即要么Show(1)进入锁区块内要么Show(2)进入。不可以同时进入。因为使用泛型同类型<int>时,其内部静态变量是共用的。
privatevoidobject sender, EventArgs e)

{

int>.Show(1); 

int>.Show(2);

int>.Show(2);

}

publicclasshuanghai<T>

{

privatestaticreadonlyobjectnewobject();

publicstaticvoidint index)

    { 

forint i = 0; i< 5; i++)

          {

int k = i;

             Task.Run(() =>

                 {

lock (hhLock2) {  

$"{k}Star{index}");

                     Thread.Sleep(2000);

$"{k}End{index}");

                     }

                 });

           } 

    }

}

    结果:

2Star1

2End1

0Star1

0End1

1Star2

1End2

4Star2

4End2

1Star1

1End1

3Star1

3End1

4Star1

4End1

0Star2

0End2

2Star2

2End2

3Star2

3End2
  1. 假如上述这样调用,两个就可以并发:
int>.Show(1);

string>.Show(2);
  1. 是否能并发,就看lock ()内的变量是否是用同一个。