2019年2月28日 星期四

【分析】淺談C#中Control的Invoke與BeginInvoke在主副線程中的執行順序和區別(SamWang)

https://www.cnblogs.com/wangshenhe/archive/2012/05/25/2516842.html

https://www.youtube.com/watch?v=oCtTgYak2Rw

【分析】淺談C#中Control的Invoke與BeginInvoke在主副線程中的執行順序和區別(SamWang)

  今天無意中看到有關Invoke和BeginInvoke的一些資料,不太清楚它們之間的區別。所以花了點時間研究了下。
  據msdn中介紹,它們最大的區別就是BeginInvoke屬於異步執行的。
  • Control. Invoke方法(Delegate) :在擁有此控件的基礎窗口句柄的線程上執行指定的委託。
  • Control.BeginInvoke方法( Delegate) :在創建控件的基礎句柄所在線程上異步執行指定委託。
 msdn說明:
控件上的大多數方法只能從創建控件的線程調用。 如果已經創建控件的句柄,則除了InvokeRequired屬性以外,控件上還有四個可以從任何線程上安全調用的方法,它們是:InvokeBeginInvokeEndInvokeCreateGraphics 在後台線程上創建控件的句柄之前調用CreateGraphics可能會導致非法的跨線程調用。 對於所有其他方法調用,則應使用調用(invoke)方法之一封送對控件的線程的調用。 調用方法始終在控件的線程上調用自己的回調。
  
  於是用下面的代碼進行初步的測試:  
  1.主線程調用Invoke   
複製代碼
1          ///  <summary> 
2          /// 直接調用Invoke
 3          ///  </summary> 
4          private  void TestInvoke()
 5          {
 6              listBox1.Items.Add( " --begin-- " );
 7              listBox1.Invoke ( new Action(() =>
 8              {
 9                  listBox1.Items.Add( " Invoke " );
 10              }));
 11  
12              Thread.Sleep( 1000 );
 13             listBox1.Items.Add( " --end-- " );
 14          }
複製代碼
輸出:    
  
  從輸出結果上可以看出,Invoke被調用後,是馬上執行的。這點很好理解。

  2.主線程調用BeginInvoke
複製代碼
1          ///  <summary> 
2          /// 直接調用BeginInvoke
 3          ///  </summary> 
4          private  void TestBeginInvoke()
 5          {
 6              listBox1.Items.Add( " --begin-- " );
 7              var bi = listBox1.BeginInvoke( new Action(() =>
 8              {
 9                  // Thread.Sleep(10000); 
10                  listBox1.Items.Add( " BeginInvoke " );
 11              }));
 12             Thread.Sleep( 1000 );
 13              listBox1.Items.Add( " --end-- " );
 14          }
複製代碼
輸出:  
  
  從輸出能看出,只有當調用BeginInvoke的線程結束後,才執行它的內容。

  不過有兩種情況下,它會馬上執行:
  使用EndInvoke,檢索由傳遞的IAsyncResult表示的異步操作的返回值。
複製代碼
        ///  <summary> 
        /// 調用BeginInvoke、EndInvoke
         ///  </summary> 
        private  void TestBeginInvokeEndInvoke()
        {
            listBox1.Items.Add( " --begin-- " );
             var bi = listBox1.BeginInvoke( new Action(() =>
            {
                Thread.Sleep( 1000 );
                listBox1.Items.Add( " BeginInvokeEndInvoke " );
            }));
            listBox1.EndInvoke(bi);
            listBox1.Items.Add( " --end-- " );
        }
複製代碼
輸出:  
  
   
  同一個控件調用Invoke時,會馬上執行先前的BeginInvoke
複製代碼
        ///  <summary> 
        /// 調用BeginInvoke、Invoke
         ///  </summary> 
        private  void TestBeginInvokeInvoke()
        {
            listBox1.Items.Add( " --begin-- " );
            listBox1.BeginInvoke( new Action(() =>
                {
                    Thread.Sleep( 1000 );
                    listBox1.Items.Add( " BeginInvoke " );
                }));
            listBox1.Invoke( new Action(() =>
                {
                    listBox1.Items.Add( " Invoke " );
                }));
            listBox1.Items.Add( " --end-- " );
        }
複製代碼
輸出:
  

  注:在主線程中直接調用Invoke、BeginInvoke、EndInvoke都會造成阻塞。所以應該利用副線程(支線線程)調用。

  3.支線線程調用Invoke
  創建一個線程,並在線程中調用Invoke,同時測試程序是在哪個線程中調用Invoke。 
複製代碼
1          ///  <summary> 
2          /// 線程調用Invoke
 3          ///  </summary> 
4          private  void ThreadInvoke()
 5          {
 6              listBox1.Items.Add( " --begin-- " );
 7              new Thread( () =>
 8              {
 9                  Thread.CurrentThread.Name = " ThreadInvoke " ;
 10                  string temp = " Before! " ;
 11                  listBox1.Invoke( newAction(() =>
 12                      {
 13                          Thread.Sleep( 10000 );
 14                          this .listBox1.Items.Add(temp += " Invoke! " + Thread.CurrentThread.Name);
 15                      }));
 16                  temp += " After! " ;
 17              }).Start();
 18              listBox1.Items.Add( " --end-- " );          
 19          }
 20  
21  
22          private  void button1_Click(object sender, EventArgs e)
 23          {
 24              Thread.CurrentThread.Name = " Main " ;
 25              ThreadInvoke();
 26          }
 27  
28          private  void button2_Click( object sender, EventArgs e)
 29          {
 30              listBox1.Items.Add( " button2_Click " );
 31          }
複製代碼

輸出:  
  
  • Invoke的委託在主線程中執行
  • Invoke在支線程中調用也會阻塞主線程。當點擊button1後,我試圖去點擊button2,卻發現程序被阻塞了)
  • Invoke還會阻塞支線程(因為輸出結果中沒有出現After)  
  接著來測試下在支線程中調用BeginInvoke. 
  4.支線線程調用BeginInvoke 
複製代碼
1          ///  <summary> 
2          /// 線程調用BeginInvoke
 3          ///  </summary> 
4          private  void ThreadBeginInvoke()
 5          {
 6              listBox1.Items.Add( " --begin-- " );
 7              new Thread( () =>
 8              {
 9                  Thread.CurrentThread.Name = " ThreadBeginInvoke " ;
 10                  string temp = " Before! " ;
 11                  listBox1.BeginInvoke(new Action(() =>
 12                  {
 13                      Thread.Sleep(10000); 
 14                      this .listBox1.Items.Add(temp += " Invoke! " + Thread.CurrentThread.Name);
 15                  }));
 17                  temp += " After! " ;
 18              }).Start();
 19              listBox1.Items.Add( " --end-- " );
 20          }
 21  
22  
23          private  void button1_Click( object sender, EventArgs e)
24          {
 25              Thread.CurrentThread.Name = " Main " ;
 26              ThreadBeginInvoke();
 27          }
 28  
29          private  void button2_Click( object sender, EventArgs e)
 30          {
 31              listBox1.Items.Add( " button2_Click " );
 32          }
複製代碼


輸出:    
  
  • BeginInvoke在主線程中執行。
  • BeginInvoke在支線程中調用也會阻塞主線程。
  • BeginInvoke相對於支線程是異步的。 

總結:  
  以下為了方便理解,假設如下:
    主線程表示Control.Invoke或Control.BeginInvoke中Control所在的線程,即創建該創建的線程。(一般為UI線程)
    支線程表示不同於主線程的調用Invoke或BeginInvoke的線程。
  • Control的Invoke和BeginInvoke的委託方法是在主線程,即UI線程上執行(也就是說如果你的委託方法用來取花費時間長的數據,然後更新界面什麼的,千萬別在主線程上調用Control.Invoke和Control.BeginInvoke,因為這些是依然阻塞UI線程的,造成界面的假死)
  • Invoke會阻塞主支線程,BeginInvoke只會阻塞主線程,不會阻塞支線程!因此BeginInvoke的異步執行是指相對於支線程異步,而不是相對於主線程異步。(從最後一個例子就能看出,程序運行點擊button1)
                                       SamWang
                                       2012-05-25

[C#.NET][Thread] 背景執行緒與前景執行緒的差別Thread.IsBackground

https://dotblogs.com.tw/yc421206/2011/01/04/20574

2019年2月25日 星期一

2019年2月20日 星期三

2019年2月13日 星期三

[C#] 啟動應用程式並且傳入參數

https://dotblogs.com.tw/atowngit/2009/12/26/12681

Socket 及 websocket

socket

http://dangerlover9403.pixnet.net/blog/post/177091473-%5B%E5%BF%83%E5%BE%97%5D-study-about-socket

https://www.youtube.com/watch?v=jTs5URRQ_kM

https://www.youtube.com/watch?v=4pPIt6M-XlU

https://www.youtube.com/watch?v=PWaZIrqDhjY

https://www.youtube.com/watch?v=oCtTgYak2Rw

websocket

https://www.youtube.com/watch?v=EpjYiP7yZ20&list=PLL7AlhappBW-IJk71v7phcPvsiglRjRje&index=5

https://www.youtube.com/watch?v=CKIWIk91yzU&list=PLL7AlhappBW-IJk71v7phcPvsiglRjRje&index=4


https://www.youtube.com/watch?v=ArfSClPwmVc&list=PLL7AlhappBW-IJk71v7phcPvsiglRjRje&index=2

https://www.youtube.com/watch?v=Gvins98lu6U&index=3&list=PLL7AlhappBW-IJk71v7phcPvsiglRjRje

https://www.youtube.com/watch?v=Gvins98lu6U&list=PLL7AlhappBW-IJk71v7phcPvsiglRjRje&index=3

https://www.youtube.com/watch?v=7HJVnyziLdY&list=PLL7AlhappBW-IJk71v7phcPvsiglRjRje&index=1


[C#]兩個程式之間的溝通-使用Pipe方式

https://www.huanlintalk.com/2013/01/aspnet-web-api-message-handlers.html

http://gienmin.blogspot.com/2016/10/c-pipe.html

https://miphix-note.blogspot.com/2018/05/c-named-pipe.html

MSDN Magazine 閱讀心得: Stream Pipeline


https://columns.chicken-house.net/2008/01/19/msdn-magazine-%E9%96%B1%E8%AE%80%E5%BF%83%E5%BE%97-stream-pipeline/

2019年2月9日 星期六

2018-03-28 [C#]Multiple thread和非同步的差異,並正確自訂非同步的方式

https://dotblogs.com.tw/kinanson/2018/03/28/075558

https://dotblogs.com.tw/mileslin/2016/03/11/230450


C# 非同步程式設計的 async void 與 async Task 的差異

https://csharpkh.blogspot.com/2017/10/c-async-void-async-task.html

C# 非同步程式設計的 async void async Task 的差異

今天,想來談談絕大部分的 C# 開發者都會產生這樣的不正確的 C# 程式碼寫法與用法,那就是當我們採用 工作式非同步模式 (TAP, Task-based Asynchronous Pattern) 進行非同步的程式開發的時候,並且想要寫個非同步運作的分法,在很多時候,您會想說,底下的方法,就是一個非同步運作的程式碼方法。
在這裡,我們在方法 M1 前面使用了 async 修飾詞,不過,這個方法並沒有任何物件值需要回傳,因此,在底下的程式碼中,您看到了這個方法使用了 async void

在工作式非同步模式的方法中,回傳值的類型僅有 Task / Task<T>,前者代表沒有任何實際物件要回傳,而後者表示要回傳型別為 T 的物件;不過,那我們為什麼又可以使用 void 這個關鍵字呢?通常,我們會在綁定事件的方法上,需要使用 async void 這樣的標示,這是因為這些事件的函式簽章就是僅支援 void 這樣的回傳值,另外,我們在這個綁定的事件方法中,又需要撰寫工作式非同步模式的功能,因此,又需要在這個事件方法前面,加入 async 這個修飾詞。

所以,除了是在綁定事件的方法內,其他任何情況下,對於您開發的工作式非同步模式方法,就不需要使用async void這樣的回傳值標示。
上面的說明,很重要,很重要,很重要

Wait() 會變同步阻塞一直到Task完成

https://blog.darkthread.net/blog/await-task-block-deadlock/?fbclid=IwAR14EVpNctRsjyoDNTgguv91i8FCKd0piP224xAGK3J_f4UZUr6fvn_iW5Q

using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace asyn2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
try
{
RunAsync().Wait();
MessageBox.Show("have to wait RunAsync()跑完嗎? ================ ");

}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}

private async Task RunAsync()
{
CancellationTokenSource cts = new CancellationTokenSource(10000);
CancellationToken ct = cts.Token;
string googleSource = await DownloadSourceAsync("https://www.google.com.tw/", ct);
MessageBox.Show(googleSource.Length.ToString());
}
private async Task<string> DownloadSourceAsync(string url, CancellationToken ct)
{
using (HttpClient httpClient = new HttpClient())
{
if (ct.IsCancellationRequested)
{
ct.ThrowIfCancellationRequested();
}
string source = await httpClient.GetStringAsync(url);
return source;
}
}
}
}


用.Wait() 會變同步阻塞一直到Task完成。
用await 會非同步等待,一直到Task完成。


你用了.Wait()造成了問題,你應該一路用async/await走下去才對。
為何用.Wait()會卡死?
因為而你的Task RunAsync() 中又去叫用DownloadSourceAsync 做 httpClient.GetStringAsync()
它根本就完成不了。 你可以在debug mode中,於string source = await httpClient.GetStringAsync(url); 這行放中斷點。
當執行到這中斷點時,你按下F11(快捷鍵:逐行執行當下那一行),你會發現你什麼都不能動了,完全阻塞,UI也都完全無反應。

要解決這問題很簡單,把.Wait()換掉,改用await
把private void button1_Click(object sender, EventArgs e)
改private async void button1_Click(object sender, EventArgs e)
還有
把RunAsync().Wait();
改await RunAsync();
你的程式就能動了,迴避掉了同步阻塞。
並且在MessageBox.Show(googleSource.Length.ToString()); 等待按下"確定"後(即RunAsync()完成後),再到 MessageBox.Show("have to wait RunAsync()跑完嗎? ================ "); 

另一解法是直接用ConfigureAwait(false)
把RunAsync().Wait();
改RunAsync().ConfigureAwait(false);
那麼程式就會一路往前衝,直接跳出兩個MessageBox!它無視await RunAsync();的結果而不等待。
但是我很不建議這樣做,因為ConfigureAwait(False)不應該被加在UI線程中(你的button1_Click就是UI線程)。
而且不建議以同步的方式執行非同步的呼叫,建議在UI線程中應該一路用async/await走下去。

await與Task.Result/Task.Wait()的Deadlock問題

https://blog.darkthread.net/blog/await-task-block-deadlock/?fbclid=IwAR1WCksZ3dZt6Yn28EdcN9qBNN_31FDh7a_o1O9RjUYUUsJaLgT2aGCHkg0

2019年2月8日 星期五

[C#] Web API - HttpClient 入門

http://marcus116.blogspot.com/2018/02/c-web-api-httpclient.html

https://dotblogs.com.tw/joysdw12/2013/06/03/web-api-httpclient-windows-form

https://csharpkh.blogspot.com/2017/10/c-httpclient-webapi.html

2019年2月7日 星期四

簡介.NET 4.0的多工執行利器--Task

https://blog.darkthread.net/blog/net4-task/


C# task用法
C# Task 的用法

其實Task跟線程池ThreadPool的功能類似,不過寫起來更為簡單,直觀。代碼更簡潔了,使用Task來進行操作。可以跟線程一樣可以輕松的對執行的方法進行控制。

順便提一下,配合CancellationTokenSource類更為可以輕鬆的對Task操作的代碼進行中途終止運行。

創建Task

創建Task有兩種方式,一種是使用構造函數創建,另一種是使用 Task.Factory.StartNew 進行創建。如下代碼所示

1.使用構造函數創建Task
Task t1 = new Task(MyMethod);

2.使用Task.Factory.StartNew 進行創建Task
Task t1 = Task.Factory.StartNew(MyMethod);

其實這兩種方式都是一樣的,Task.Factory 是對Task進行管理,調度管理這一類的。好學的夥伴們,可以深入研究。這不是本文的範疇,也許會在後面的文章細說。

運行 Task

運行Task的兩種方式,在上面我們已經提到過了,一種等待運行完畢,另一種則等待所有運行完畢。不過這裏還有一種就是異步運行,跟使用多線程一樣,調用Task對象中的Start()方法即可。看看下面這個控制臺示例。純粹是WaitAllWait的話,僅僅是等待。而不是執行。所以我們還需要調用Start()方法

static void Main(string[] args)
{
    Task t1 = new Task(MyMethod);
    t1.Start();
    Console.WriteLine("主線程代碼運行結束");
    Console.ReadLine();
}

static void MyMethod()
{
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine(DateTime.Now.ToString());
        Thread.Sleep(1000);
    }
}

TaskWebApi中的運用

static async Task MainAsync()
{
  // your async code here
  var _httpClient = new HttpClient();
  _httpClient.BaseAddress = new Uri("http://localhost:48959/");
  token = await GetAccessToken();
  Console.WriteLine(token);
  Console.WriteLine(token.Length);
  _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
 Console.WriteLine(await (await _httpClient.GetAsync("/currnetUser")).Content.ReadAsStringAsync());
 Console.ReadKey();
}
static void Main(string[] args)
{
 MainAsync().Wait();
}
在一個線程調用Wait方法時,系統會檢查線程要等待的Task是否已經開始執行,如果任務正在執行,那麽這個Wait方法會使線程阻塞,直到Task運行結束為止。

2019年2月6日 星期三

Task 類別來處理非同步工作 task.run

https://www.huanlintalk.com/2013/06/csharp-notes-multithreading-6-tpl.html


static void Main(string[] args)
{
    // 寫法 1 - .NET 2 開始提供
    ThreadPool.QueueUserWorkItem(state => MyTask());

    // 寫法 2 - .NET 4 開始提供 Task 類別。
    var t = new Task(MyTask);   // 等同於 new Task(new Action(MyTask));
    t.Start();

    // 寫法 3 - 也可以用靜態方法直接建立並開始執行工作。
    Task.Factory.StartNew(MyTask);

    // 寫法 4 - .NET 4.5 的 Task 類別新增了靜態方法 Run。
    Task.Run(() => MyTask());

    Console.ReadLine();
}

static void MyTask()
{
    Console.WriteLine("工作執行緒 #{0}", Thread.CurrentThread.ManagedThreadId);
}


CancellationTokenSource 類別的幾個常用屬性和方法

https://www.huanlintalk.com/2013/06/csharp-notes-multithreading-5.html

https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/597370/

Task.FromResult 用法與注意事項

Task.FromResult 用法與注意事項
Task 類別有一個靜態方法可用來傳回已完成的工作,這個方法是 FromResult()。
底下是一個簡單範例:


Task<int>task1=Task.FromResult(10);


什麼時候會用到它呢?
一個常見的應用場合是:在非同步方法中,希望以同步的方式傳回結果
舉例來說,假設你現在要設計一個支援非同步處理的快取物件,你先定義了如下介面:


Interface IMyCache
{
Task<string>GetDataAsync();
}


這表示,你預期將來實作 IMyCache 介面時,GetDataAsync 會是個非同步方法。
然而,在某種特殊情況下(例如撰寫單元測試),你可能不需要複雜的非同步操作,
而只需要直接傳回一個現成的結果。那麼,在撰寫 GetDataAsync 方法時,
便可以用上 Task.FromResult。如下所示:


Class MyCache : IMyCache
{
Public async Task<string>GetDataAsync()
{
 Return await Task.FromResult("hello");
 // 此寫法有點問題,稍後說明。
}
}


以下程式碼則示範了如何在 Console 應用程式中使用 MyCache 類別的 GetDataAsync 方法:


Static void Main(string[] args)
{
 Task.Run(async() =>
  {
   IMyCache myCache =new MyCache();
   Var s = await myCache.GetDataAsync();
   System.Console.WriteLine(s);
  });
Console.ReadLine();
}


等等,上述範例的寫法其實有個問題:在沒有非同步操作的地方多餘地使用了
async 和 await 關鍵字。接著要繼續討論這個問題

不要 await Task.FromResult 呼叫


假設你就是負責撰寫 MyCache 類別的人,那麼,你自然知道你寫的 GetDataAsync 方法
其實並沒有執行任何非同步工作,因為它是呼叫
Task.FromResult 來直接(立刻)傳回一個已經完成的工作
然而,剛才的 GetDataAsync 方法卻使用了 async 和 await 關鍵字,
而這兩個關鍵字會使編譯器產生一些額外的程式碼,以便用來處理非同步呼叫的等待以
及環境切換等處理。既然 Task.FromResult 會直接傳回現成的 Task 物件,那麼編譯器
所產生的那些額外的程式碼也就都是多餘的了


簡單地說,我們應該遵循這個建議:不要 await 一個 Task.FromResult 呼叫。
因此,先前的 GetDataAsync 方法應該把 async 和 await 去掉,變成這樣:


Public Task<string>GetDataAsync()
{
Return Task.FromResult("hello");
}


你可以看到,在定義 IMyCache 介面時,雖然預期 GetDataAsync 是個非同步方法
(注意方法名稱有 Async 後綴),但實作時仍有可能採取同步的方式——
當你看到這樣的寫法時,應特別留意是否無意間在非同步呼叫的流程中混雜了
同步/阻斷式呼叫,例如底下這個錯誤示範:


Public Task<string>GetDataAsync()
{
 String s =System.IO.File.ReadAllText(@"C:\temp\big.txt");
 // 錯誤示範!
 returnTask.FromResult(s);

}

範例

private void button3_Click(object sender, EventArgs e) { MessageBox.Show("Hello World"); MyVoidAsyncMethod().Wait(); MyVoidAsyncMethod2().Wait(); MyVoidAsyncMethod3(); //no await/async } public static async Task MyVoidAsyncMethod() { //We can await only methods which return Task or Task<T>
so this can be done by returning value from awaited completed task: //requires .net 4.6 await Task.CompletedTask; MessageBox.Show("After Task.CompletedTask"); } //Task.FromResult(0) public static async Task MyVoidAsyncMethod2() { //We can await only methods which return Task or Task<T> so this can be done by returning value from awaited completed task:
//requires .net 4.5 await Task.FromResult(0); MessageBox.Show("After Task.FromResult(0)"); } public static Task MyVoidAsyncMethod3() { MessageBox.Show("no async/await Task.FromResult(0)"); return Task.FromResult(0); }


WPF聊天室应用(ASP.NET Core SignalR)

  WPF聊天室应用(ASP.NET Core SignalR) https://www.bilibili.com/video/BV1Q741187Si?p=2 https://www.bilibili.com/video/BV1UV411e75T?from=search&...