2019年2月1日 星期五

為何在使用 ReaderWriterLockSlim 與 非同步 await 關鍵字的時候,會造成系統掛掉

為何在使用 ReaderWriterLockSlim 與 非同步 await 關鍵字的時候,會造成系統掛掉

最近遇到一個問題,當我們建立一個 ASP.NET Core 的空白專案的時候,接著,在 Startup 類別中,建立一個靜態 ReaderWriterLockSlim 物件,而我們最終的目的是要能夠在 Configure 方法內,使用 app.Run 來寫入當時的時間到特定文字檔案內,用來記錄這個專案是何時啟用的,因此,將會使用底下的範例程式碼,當要把時間寫入到檔案內的時候,使用 ReaderWriterLockSlim.EnterWriteLock() 方法進行通知其他執行緒,現在這個執行緒需要開始進行鎖定,若同時也有其他的執行緒會用到 ReaderWriterLockSlim ,此時,該執行緒將會進入被 封鎖 Block 的狀態,剛剛那個執行緒呼叫了 ReaderWriterLockSlim.ExitWriteLock(),以便解除執行緒同步的狀態。
C Sharp / C#
public class Startup
{
    static ReaderWriterLockSlim lock1 = new ReaderWriterLockSlim();
    public void ConfigureServices(IServiceCollection services) { }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        app.Run(async (context) =>
        {
            lock1.EnterWriteLock();
            try
            {
                await File.AppendAllTextAsync("C:\\MyLog.log", DateTime.Now + "\n");
            }
            finally
            {
                lock1.ExitWriteLock();
            }
            await context.Response.WriteAsync("Hello World!");
        });
    }
}
看似一切都很美好,可是,當您啟動該專案之後,卻會得到了底下的錯誤訊息,讓人丈二金剛摸不著頭腦,明明就是按照文件說明的用法,卻是無法使用,這到底發生了甚麼問題呢?
C Sharp / C#
An unhandled exception occurred while processing the request.
UnauthorizedAccessException: Access to the path 'd:\MyLog.log' is denied.
System.IO.FileStream.ValidateFileHandle(SafeFileHandle fileHandle)

UnauthorizedAccessException: Access to the path 'd:\MyLog.log' is denied.
System.IO.FileStream.ValidateFileHandle(SafeFileHandle fileHandle)
System.IO.FileStream.CreateFileOpenHandle(FileMode mode, FileShare share, FileOptions options)
System.IO.FileStream..ctor(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
System.IO.File.AsyncStreamWriter(string path, Encoding encoding, bool append)
System.IO.File.AppendAllTextAsync(string path, string contents, Encoding encoding, CancellationToken cancellationToken)
System.IO.File.AppendAllTextAsync(string path, string contents, CancellationToken cancellationToken)
WebApplication4.Startup+<>c+<<Configure>b__2_0>d.MoveNext() in Startup.cs
+
                    await File.AppendAllTextAsync("d:\\a.log", DateTime.Now + "\n");
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
想要能夠解決這個問題,你需要明瞭 await 這個非同步關鍵字到底是怎麼運作的以及 執行緒仿射 Thread-affine 這兩個內容。
當您在程式內使用了 await 關鍵字,C# 編譯器將會把你的程式碼拆成兩個區塊,一個是在 await 關鍵字之前要等候的程式碼,也就是呼叫了一個非同步的運算之前的所有程式碼,這些程式碼將會在您呼叫 app.Run 這個委派方法當時的執行緒來執行,這個時候,我們也在這個執行緒內呼叫了 ReaderWriterLockSlim.EnterWriteLock() 方法,取得了一個執行緒同步的專屬使用權,其他的執行緒是無法進入到相同的程式碼區段的;另外一個就是當非同步方法執行完畢之後,在 await 關鍵字之後的所有程式碼,這裡將會拆出成為另外一個程式碼區塊。
由於這是個 Console 類型的應用程式,他的 SynchronizationContext 同步處理內容 並不存在,因此,在 await 之後的程式碼將會在另外一個執行緒中來運行(實際上,會從 ThreadPool 執行緒集區中,要求一個可用的執行緒來執行這些程式碼)。
但是,這樣為什麼會造成這個專案發生例外異常呢?這個時候,就需要來了解 執行緒仿射 Thread-affine,當想要使用 ReaderWriterLockSlim 類別作為執行緒同步問題的解決方案的時候, ReaderWriterLockSlim.EnterWriteLock() 與 ReaderWriterLockSlim.ExitWriteLock() 必須要同一個執行緒下來執行。在 UnauthorizedAccessException 說明文件中,可以看到這個例外異常是這個意思:當作業系統因為 I/O 錯誤或特定類型的安全性錯誤而拒絕存取時,所擲回的例外狀況。
因此,我們需要讓執行 await 關鍵字前後的程式區段都能夠在同一個執行緒之下,才能夠避免這樣的問題,這裡提出兩個解決方案,那就是在 app.Run 內的委派方法,不要使用 await 非同步的方式來執行,而是使用同步的方式來執行: File.AppendAllTextAsync(@"d:\\a.log", DateTime.Now + "\n").Wait();,如同底下的程式碼
C Sharp / C#
public class Startup
{
    static ReaderWriterLockSlim lock1 = new ReaderWriterLockSlim();
    public void ConfigureServices(IServiceCollection services) { }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        app.Run(async (context) =>
        {
            lock1.EnterWriteLock();
            try
            {
                 File.AppendAllTextAsync(@"d:\\a.log", DateTime.Now + "\n").Wait();
            }
            finally
            {
                lock1.ExitWriteLock();
            }
            await context.Response.WriteAsync("Hello World!");
        });
    }
}
第二種作法則是使用 Nito.AsyncEx 這個套件,裡面有提供非同步運算可以使用的 AsyncReaderWriterLock ,這樣當我們使用 await 進行非同步計算的時候,就不會產生問題了。
C Sharp / C#
public class Startup
{
    static AsyncReaderWriterLock lock1 = new AsyncReaderWriterLock();
    public void ConfigureServices(IServiceCollection services)
    {
    }
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        app.Run(async (context) =>
        {
            using (await lock1.WriterLockAsync())
            {
                await File.AppendAllTextAsync(@"D:\Vulcan\Projects\ReaderWriterLockSlimAsyncTest\tmp.txt", DateTime.Now + "\n")
                  ;
            }
            await context.Response.WriteAsync("Hello World!");
        });
    }
}
Monitors (and locks) / Reader-writer locks / Mutex 也同樣的具有 執行緒仿射 Thread-affine 特性,使用上要特別注意



進一步了解 ReaderWriterLockSlim Mutex Lock 與 執行緒仿射 Thread-affine

進一步了解 ReaderWriterLockSlim Mutex Lock 與 執行緒仿射 Thread-affine

在上一篇文章 為何在使用 ReaderWriterLockSlim 與 非同步 await 關鍵字的時候,會造成系統掛掉 中,我們建立了一個 ASP.NET Core 專案,並且使用了 ReaderWriterLockSlim 來確保對於要寫入到同一個檔案的過程中,可以確保不同執行緒不會同時寫入這個檔案,而造成資料不一致性的問題,可是,卻遇到了專案當掉的問題;我們將會在這篇文章中,簡化這個問題,瞭解出到底發生了甚麼問題,以及更多這方面的問題。
首先,我們建立一個 .NET Core 主控台應用程式,並且填入底下的程式碼:
C Sharp / C#
class Program
{
    static ReaderWriterLockSlim lock1 = new ReaderWriterLockSlim();
    static void Main(string[] args)
    {
        ThreadPool.QueueUserWorkItem(AsyncMethod);
        Console.WriteLine("Press any key for continuing...");
        Console.ReadKey();
    }
    private static async void AsyncMethod(object state)
    {
        Console.WriteLine($"進入封鎖前的 執行緒 : {Thread.CurrentThread.ManagedThreadId}");
        lock1.EnterWriteLock();
        try
        {
            Console.WriteLine($"等候非同步工作前的 執行緒 : {Thread.CurrentThread.ManagedThreadId}");
            await File.AppendAllTextAsync(@"D:\Vulcan\Projects\ThreadAffine\ThreadAffine\MyLog.log", DateTime.Now + "\n");
            Console.WriteLine($"繼續非同步工作的 執行緒 : {Thread.CurrentThread.ManagedThreadId}");
        }
        finally
        {
            Console.WriteLine($"準備進入解除封鎖前 執行緒 : {Thread.CurrentThread.ManagedThreadId}");
            lock1.ExitWriteLock();
            Console.WriteLine($"準備進入解除封鎖後 執行緒 : {Thread.CurrentThread.ManagedThreadId}");
        }
        Console.WriteLine("Hello World!");
    }
}
在這個測試程式碼中,一樣先產建立一個 ReaderWriterLockSlim 物件,接著呼叫 EnterWriteLock() 方法,準備進入寫入封鎖模式,禁止其他執行緒可以進入到寫入模式內,並且使用 await 關鍵是進行非同步運算,最後,呼叫 ExitWriteLock() 方法,解除寫入封鎖模式。
不過,在這個測試程式碼中,將會在不同的關鍵點,輸出當時 .NET 受管理的執行緒 ID值,用來判斷整段程式碼是否都在同一個執行緒來執行。當然,若您瞭解了 async / await 的運作模式,就會知道,當程式碼執行到 await 非同步方法的時候,就會進入到等候狀態下;當非同步方法執行完畢之後,就會緊接著剛剛等候的程式碼點,繼續接著執行,不過,因為這是一個 Console 模式的應用程式,並沒有 SynchronizationContext 同步內容存在,所以,在 await 等候之後的程式碼,將會從 執行緒集區 ThreadPool 內,取得一個可用的執行緒,繼續來執行。
現在,讓我們來實際執行這段程式碼,看看會得到甚麼樣的輸出內容,以及又會發生甚麼問題呢?
Console
Press any key for continuing...
進入封鎖前的 執行緒 : 3
等候非同步工作前的 執行緒 : 3
繼續非同步工作的 執行緒 : 4
準備進入解除封鎖前 執行緒 : 4
當程式一執行的時候,我們得到到一個例外異常,例外異常訊息內容為:System.Threading.SynchronizationLockException: 'The write lock is being released without being held.' ,並且,整個專案是在 lock1.ExitWriteLock(); 敘述中發生了例外異常。
透過專案執行結果的輸出內容,我們得知道,這個專案一開始執行的時候,是在 執行緒 ID=3 下來執行程式碼,不過,當執行 await 等候之後,這個測試程式碼繼續執行的時候,切換到了 執行緒 ID=4,所以,當該執行緒執行到 lock1.ExitWriteLock() 敘述的時候,發現到在 執行緒 ID=4 內,並沒有執行過 lock1.EnterWriteLock() 敘述,因此,就會拋出例外異常,這是因為 ReaderWriterLockSlim 具備了 執行緒仿射 Thread-affine 特性,必須要在同一個執行緒下先執行 lock1.EnterWriteLock() ,接著再呼叫 lock1.ExitWriteLock() 才能夠正常解除封鎖。在這個範例中,因為這兩個敘述分別在不同執行緒下執行,所以,就會造成例外異常。
Console
System.Threading.SynchronizationLockException
  HResult=0x80131518
  Message=The write lock is being released without being held.
  Source=System.Private.CoreLib
  StackTrace: 
   at System.Threading.ReaderWriterLockSlim.ExitWriteLock()
   at ThreadAffine.Program.<AsyncMethod>d__2.MoveNext() in D:\Vulcan\Projects\ThreadAffine\ThreadAffine\Program.cs:line 57
那麼,如同前一篇文章提到的:Monitors (and locks) / Reader-writer locks / Mutex 也同樣的具有 執行緒仿射 Thread-affine 特性 這句話,我們撰寫了底下另外一個測試範例,在這裡,則會使用 Mutex 這個執行緒同步 物件。
現在,呼叫了 mut.WaitOne() 要進入 Mutex 並進入鎖定模式,緊接著執行非同步的檔案寫入,因此,透過這樣的機制,在同一個時間點,僅會有一個執行緒可以寫入到這個檔案內,當檔案寫入完成之後,在這裡將會呼叫 mut.ReleaseMutex() 方法來離開 Mutex 並且解除封鎖。
現在,讓我們來執行看看:
C Sharp / C#
class Program
{
    static object myLocker;
    private static Mutex mut = new Mutex();
    static void Main(string[] args)
    {
        ThreadPool.QueueUserWorkItem(AsyncMethod);
        Console.WriteLine("Press any key for continuing...");
        Console.ReadKey();
    }
    private static async void AsyncMethod(object state)
    {
        Console.WriteLine($"進入 Mutex 封鎖前的 執行緒 : {Thread.CurrentThread.ManagedThreadId}");
        mut.WaitOne();
        Console.WriteLine($"等候非同步工作前的 執行緒 : {Thread.CurrentThread.ManagedThreadId}");
        await File.AppendAllTextAsync(@"D:\Vulcan\Projects\ThreadAffine\ThreadAffine\MyLog.log", DateTime.Now + "\n");
        Console.WriteLine($"繼續非同步工作的 執行緒 : {Thread.CurrentThread.ManagedThreadId}");
        Console.WriteLine($"進入 Mutex 解除封鎖前的 執行緒 : {Thread.CurrentThread.ManagedThreadId}");
        mut.ReleaseMutex();
        Console.WriteLine($"進入 Mutex 解除封鎖後的 執行緒 : {Thread.CurrentThread.ManagedThreadId}");
    }
}
從執行後的輸出結果,將會看到僅有底下四行文字內容輸出,並且,程式在 mut.ReleaseMutex() 這行敘述上發生了例外異常,他的例外異常詳細內容如下所示,而異常訊息為 Object synchronization method was called from an unsynchronized block of code. ,這說明了這個程式呼叫 WaitOne() & ReleaseMutex()分別在不同的執行緒下,因此,將會產生這樣的例外異常錯誤。
Console
Press any key for continuing...
進入 Mutex 封鎖前的 執行緒 : 3
等候非同步工作前的 執行緒 : 3
繼續非同步工作的 執行緒 : 4
進入 Mutex 解除封鎖前的 執行緒 : 4
Console
System.ApplicationException
  HResult=0x80131600
  Message=Object synchronization method was called from an unsynchronized block of code.
  Source=System.Private.CoreLib
  StackTrace: 
   at System.Threading.Mutex.ReleaseMutex()
   at ThreadAffine.Program.<AsyncMethod>d__3.MoveNext() in D:\Vulcan\Projects\ThreadAffine\ThreadAffine\Program.cs:line 31



2019年1月30日 星期三

為何在使用 ReaderWriterLockSlim 與 非同步 await 關鍵字的時候,會造成系統掛掉

最近遇到一個問題,當我們建立一個 ASP.NET Core 的空白專案的時候,接著,在 Startup 類別中,建立一個靜態 ReaderWriterLockSlim 物件,而我們最終的目的是要能夠在 Configure 方法內,使用 app.Run 來寫入當時的時間到特定文字檔案內,用來記錄這個專案是何時啟用的,因此,將會使用底下的範例程式碼,當要把時間寫入到檔案內的時候,使用 ReaderWriterLockSlim.EnterWriteLock() 方法進行通知其他執行緒,現在這個執行緒需要開始進行鎖定,若同時也有其他的執行緒會用到 ReaderWriterLockSlim ,此時,該執行緒將會進入被 封鎖 Block 的狀態,剛剛那個執行緒呼叫了 ReaderWriterLockSlim.ExitWriteLock(),以便解除執行緒同步的狀態。
C Sharp / C#
public class Startup
{
    static ReaderWriterLockSlim lock1 = new ReaderWriterLockSlim();
    public void ConfigureServices(IServiceCollection services) { }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        app.Run(async (context) =>
        {
            lock1.EnterWriteLock();
            try
            {
                await File.AppendAllTextAsync("C:\\MyLog.log", DateTime.Now + "\n");
            }
            finally
            {
                lock1.ExitWriteLock();
            }
            await context.Response.WriteAsync("Hello World!");
        });
    }
}
看似一切都很美好,可是,當您啟動該專案之後,卻會得到了底下的錯誤訊息,讓人丈二金剛摸不著頭腦,明明就是按照文件說明的用法,卻是無法使用,這到底發生了甚麼問題呢?
C Sharp / C#
An unhandled exception occurred while processing the request.
UnauthorizedAccessException: Access to the path 'd:\MyLog.log' is denied.
System.IO.FileStream.ValidateFileHandle(SafeFileHandle fileHandle)

UnauthorizedAccessException: Access to the path 'd:\MyLog.log' is denied.
System.IO.FileStream.ValidateFileHandle(SafeFileHandle fileHandle)
System.IO.FileStream.CreateFileOpenHandle(FileMode mode, FileShare share, FileOptions options)
System.IO.FileStream..ctor(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
System.IO.File.AsyncStreamWriter(string path, Encoding encoding, bool append)
System.IO.File.AppendAllTextAsync(string path, string contents, Encoding encoding, CancellationToken cancellationToken)
System.IO.File.AppendAllTextAsync(string path, string contents, CancellationToken cancellationToken)
WebApplication4.Startup+<>c+<<Configure>b__2_0>d.MoveNext() in Startup.cs
+
                    await File.AppendAllTextAsync("d:\\a.log", DateTime.Now + "\n");
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
想要能夠解決這個問題,你需要明瞭 await 這個非同步關鍵字到底是怎麼運作的以及 執行緒仿射 Thread-affine 這兩個內容。
當您在程式內使用了 await 關鍵字,C# 編譯器將會把你的程式碼拆成兩個區塊,一個是在 await 關鍵字之前要等候的程式碼,也就是呼叫了一個非同步的運算之前的所有程式碼,這些程式碼將會在您呼叫 app.Run 這個委派方法當時的執行緒來執行,這個時候,我們也在這個執行緒內呼叫了 ReaderWriterLockSlim.EnterWriteLock() 方法,取得了一個執行緒同步的專屬使用權,其他的執行緒是無法進入到相同的程式碼區段的;另外一個就是當非同步方法執行完畢之後,在 await 關鍵字之後的所有程式碼,這裡將會拆出成為另外一個程式碼區塊。
由於這是個 Console 類型的應用程式,他的 SynchronizationContext 同步處理內容 並不存在,因此,在 await 之後的程式碼將會在另外一個執行緒中來運行(實際上,會從 ThreadPool 執行緒集區中,要求一個可用的執行緒來執行這些程式碼)。
但是,這樣為什麼會造成這個專案發生例外異常呢?這個時候,就需要來了解 執行緒仿射 Thread-affine,當想要使用 ReaderWriterLockSlim 類別作為執行緒同步問題的解決方案的時候, ReaderWriterLockSlim.EnterWriteLock() 與 ReaderWriterLockSlim.ExitWriteLock() 必須要同一個執行緒下來執行。在 UnauthorizedAccessException 說明文件中,可以看到這個例外異常是這個意思:當作業系統因為 I/O 錯誤或特定類型的安全性錯誤而拒絕存取時,所擲回的例外狀況。
因此,我們需要讓執行 await 關鍵字前後的程式區段都能夠在同一個執行緒之下,才能夠避免這樣的問題,這裡提出兩個解決方案,那就是在 app.Run 內的委派方法,不要使用 await 非同步的方式來執行,而是使用同步的方式來執行: File.AppendAllTextAsync(@"d:\\a.log", DateTime.Now + "\n").Wait();,如同底下的程式碼
C Sharp / C#
public class Startup
{
    static ReaderWriterLockSlim lock1 = new ReaderWriterLockSlim();
    public void ConfigureServices(IServiceCollection services) { }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        app.Run(async (context) =>
        {
            lock1.EnterWriteLock();
            try
            {
                 File.AppendAllTextAsync(@"d:\\a.log", DateTime.Now + "\n").Wait();
            }
            finally
            {
                lock1.ExitWriteLock();
            }
            await context.Response.WriteAsync("Hello World!");
        });
    }
}
第二種作法則是使用 Nito.AsyncEx 這個套件,裡面有提供非同步運算可以使用的 AsyncReaderWriterLock ,這樣當我們使用 await 進行非同步計算的時候,就不會產生問題了。
C Sharp / C#
public class Startup
{
    static AsyncReaderWriterLock lock1 = new AsyncReaderWriterLock();
    public void ConfigureServices(IServiceCollection services)
    {
    }
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        app.Run(async (context) =>
        {
            using (await lock1.WriterLockAsync())
            {
                await File.AppendAllTextAsync(@"D:\Vulcan\Projects\ReaderWriterLockSlimAsyncTest\tmp.txt", DateTime.Now + "\n")
                  ;
            }
            await context.Response.WriteAsync("Hello World!");
        });
    }
}
Monitors (and locks) / Reader-writer locks / Mutex 也同樣的具有 執行緒仿射 Thread-affine 特性,使用上要特別注意


2019年1月16日 星期三

TPL TAP 1 透過 Task.Status 取得正在執行中的工作狀態


TPL TAP 1 透過 Task.Status 取得正在執行中的工作狀態

當需要進行 Task 非同步的計算,可以選擇這些方法:建立一個 Task 物件 ,呼叫該物件的 Task.Start 方法、或者使用 TaskFactory.StartNew 來取得一個 Task 物件,又或者可以使用 Task.Run 方法來啟動一個 Task 非同步工作。
在這篇文章的範例中,則是使用 Task.Run 靜態方法,建立一個委派非同步工作,不過,在這個委派方法中,則是只有一行的程式碼,那就是拋出一個例外異常;由於在 Task 工作內所發生的例外異常,會被 Task 物件捕捉起來,並不會造成該應用程式異常結束,不過,在設計這樣的應用程式的時候,該如何知道這個工作發生了例外異常呢?在 Task 物件內,可以透過 Task.Status 這個屬性來得知該工作的執行結果或者當時的狀態。
其中 Task.Status 共會有這些列舉值 : Canceled , Created , Faulted , RanToCompletion , Running , WaitingForActivation , WaitingForChildrenToComplete , WaitingToRun
在底下的範例程式碼中,在主執行緒下會先使用 Task.Run 建立一個非同步委派工作,接著,主執行緒會休息 800ms ,最後,會顯示這個工作物件的狀態與最後結果的相關屬性值,分別是:IsCompleted 表示這個工作已經正常執行完成、 IsCanceled 這個工作取消了、最後是 IsFaulted 表示這個工作在執行上產生了例外異常。
C Sharp / C#
var fooTask = Task.Run( () =>
{
    throw new Exception("發生了例外異常");
});
Thread.Sleep(800);
Console.WriteLine($"Status : {fooTask.Status}");
Console.WriteLine($"IsCompleted : fooTask.IsCompleted}");
Console.WriteLine($"IsCanceled : fooTask.IsCanceled}");
Console.WriteLine($"IsFaulted : {fooTask.IsFaulted}");
var exceptionStatusX = (fooTask.Exception == null) ?"沒有 AggregateException 物件" : "有 ggregateException 物件";
Console.WriteLine($"Exception : {exceptionStatusX}")
Console.WriteLine("Press any key for continuing...")
Console.ReadKey();
現在,透過 Visual Studio 2017 的除錯組態進行這個範例程式碼除錯與執行, 可以,竟然偶而會看到底下的輸出結果,也就是看到該工作的狀態為 WaitingForActivation,想要不看這樣不正常的結果,因此,當要執行這個範例程式碼,請不要使用 Visual Studio 2017 來進行執行,因為,這樣會有可能得到 fooTask 物件的 Status 狀態值為 WaitingForActivation,所以,請在命令提示字元視窗下來執行。
Console
Status : WaitingForActivation
IsCompleted : False
IsCanceled : False
IsFaulted : False
Exception : 沒有 AggregateException 物件
Press any key for continuing...
當把這個範例程式碼,直接透過 命令提示字元視窗下來執行,此時,就會看到了所期望的結果,這個非同步委派工作最後的工作狀態應該是 Faulted,並且 Exception 這個屬性是有物件的。
Console
Status : Faulted
IsCompleted : True
IsCanceled : False
IsFaulted : True
Exception : 有 AggregateException 物件
Press any key for continuing...