2019年2月3日 星期日

打包 Xamarin.Forms 使用的 C# 程式碼片段成為 Visual Studio 2017 的擴充功能

打包 Xamarin.Forms 使用的 C# 程式碼片段成為 Visual Studio 2017 的擴充功能

在過去幾年中,由於從事於 Xamarin.Forms 專案開發與教學的工作,因此,累積了許多開發上使用的程式碼片段,這些程式碼片段大約有27個;而每次進行教學課程的時候,都會要求 Xamarin.Forms 上課學員可以把這些程式碼片段安裝起來,以便可以加快練習專案的開發。然而,幾乎都經常會有許多學員對於使用 Visual Studio 2017 程式碼片段管理員,把這些程式碼片段檔案安裝到 Visual Studio 開發環境上遇到問題,雖然,我都有提供相關作法文件與操作較學影片,不過,還是會有學員設定與使用上發生了問題。
有鑑於此,想說何不自己開發出一個 Visual Studio 擴充功能,如此,可以讓上課學員可以安裝這個擴充功能之後,便可以把這些好用的程式碼開發片段安裝到他們的 Visual Studio 2017 開發環境上;在這裡,我已經做好這個擴充功能,並且上架到 Visual Studio Marketplace 上,有興趣的人可以自行安裝 Xamarin.Forms Snippet for Prism

建立擴充功能專案

那麼,要如何把自己開發的程式碼片段打包成為 Visual Studio 程式碼片段呢?首先,請打開 Visual Studio 2017 ,從功能表中點選 [檔案] > [新增] > [專案],當 [新增專案] 對話窗顯示出來之後,請選擇 [已安裝] > [Visual C#] > [Extensibility] > [VSIX Project] 這個項目,最後,請在對話窗的下方,輸入這個專案的名稱,並且點選 [確定] 按鈕,以便完成建立這個專案。

建立程式碼片段資料夾與設定檔案屬性

請使用滑鼠右擊這個專案,建立底下階層的資料夾 [Snippets] > [CSharp] > [XamarinPrism] ,最後一個資料夾 XamarinPrism 的名稱,您可以依據您的喜好,選擇一個適合的名稱。
此時,您可以把這些程式碼片段,加入倒 [XamarinPrism] 資料夾內,不過,此時,請特別注意要進行底下的設定動作,否則,會有問題產生。
請將這個 Visual Studio 專案內的所有程式碼片段節點全選起來,並且在屬性視窗內,請先選擇這些程式碼片段檔案的 [建置動作] 屬性為 [內容],接著,請設定 [Include in VSIX] 這個屬性為 [True]。

建立與設定機碼內容

請在這個專案內,加入一個 XamarinPrismSnippet.pkgdef 文字檔案,這個檔案的名稱您可以自行設定,並不一定要叫做 XamarinPrismSnippet,接著,請在這個文字檔案內,填入底下內容。
在這裡,我們要設定當這個 Visual Studio 擴充功能在使用者電腦上安裝成功之後,需要在他的電腦上新增底下描述的機碼,在第二行的等號左邊的名稱,將會出現在程式碼片段管理員中的分類名稱,因此,您可以依據您的需要,自行調整與變更成為您需要的名稱。
[$RootKey$\Languages\CodeExpansions\CSharp\Paths]
"XamarinPrism"="$PackageFolder$\Snippets\CSharp\XamarinPrism"

修正 source.extension.vsixmanifest

現在,請在專案中,找到 source.extension.vsixmanifest 節點,使用滑鼠雙擊這個節點,便會看到一個視窗內容,請點選 [Assets] 標籤頁次;接著,請點選右方的 [New] 按鈕。
現在會看到一個 [Add New Asset] 對話窗,請在 [Type] 欄位中,選擇 [Microsoft.VisualStudio.VsPackage],接著在 [Source] 欄位中,選擇 [File on filesystem] 這個選項。
此時,會出現一個新的 [Path] 欄位,我們可以將剛剛建立好的 XamarinPrismSnippet.pkgdef 這個文字檔案名稱,輸入到 [Path] 欄位中 [XamarinPrismSnippet.pkgdef] ,完成後,請點選 [OK] 按鈕。
請在這個視窗的最右上方,看到有兩個欄位,分別是 [Author] 與 [Version] ,請適當修正這兩個欄位值。

進行測試

好的,現在我們可以開始進行這個擴充功能的除錯執行與測試了,現在,請點選工具列上的 Current Instance [Visual Studio Enterprise 2017] 這個項目 (我的電腦上安裝的 Visual Studio 2017 是屬於 Enterprise 版本),現在,將會出現一個獨立環境的 Visual Studio。
請在這個獨立測試環境的 Visual Studio 中,打開程式碼片段管理員對話窗,此時,您應該可以從 CSharp 程式碼片段群組內看到 XamarinPrism 這個選項,那就表示這個擴充功能專案已經正常設計好了。


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 特性,使用上要特別注意