2013年7月30日

C# - 非同步的async和await

7/31 更新 – async和await是.net 4.5才出現的,感謝ALEX前輩提醒~裡面內文已經更新

前一段時間,在測試Visual Studio的非同步偵錯功能,範例是一個使用async和await滿簡單的程式,但其中有一行Code,內容是寫Task.Yield(),小弟我一直搞不太懂,所花了一點時間把async和await的底層稍微看了一下( 真的只有稍微…但花了整整一整天的時間… ),看完後,也大致上了解Task.Yield(),所以小弟也在這邊紀錄一下;雖然這次是從Task.Yield()為出發點,但實際上還是會圍繞在async和await上面。

這篇文章,大致上的內容是出處於MSDN雜誌,有興趣的可以去看看原文,而這篇文章,也不太會針對async和await的用法做太多的解釋,也請大家見諒。

首先,該從哪邊談起呢??應該還是要從非同步與同步開始說起吧,基本上,這裡不會太加以敘述這方面的概念,只會簡單的說一下,同步簡單來說,就是一步接著一步,如我們平常寫的程式碼一樣,從上而下,一步一步依照著我們的步調執行。

那這樣會遇到怎要的問題呢??簡單的說,當我們遇到了大量存取,或是要消耗大量運算的時候,會碰到怎樣的問題!??,是低,就是卡住,因為會卡在那一行的程式碼嘛…

而因此,就產生了非同步的概念,非同步簡單的說,就像時空跳躍,當我們遇到需要處理大量資料,而會卡住的時候,先跳去做別的事情,然後等那大量的資料處理完後,再接著處理那大量資料後的程式碼;如果以網頁的角度來說,同步,就好像,我們按下了一個按鈕後,然後看著圈圈在那邊轉阿轉,然後像瀏覽器當掉一樣,甚麼事情都不能做;而非同步,當我們按了一個按鈕之後,還是可以到處亂點~~ ( 其實現在網頁幾乎都在跑非同步了… )

好,其實還是花了一些時間介紹- -|||,基本上,我想非同步語同步的觀念,以現在寫前端的朋友們,應該都非常熟了才對。

那我們回來看一下早期的C#寫法( 其實也不算多早阿QQ… ),如下,我們使用MSDN雜誌的Code來進行解釋~~;其實如果會寫js的朋友,應該就很有感覺,因為語法幾乎一樣阿XDD;下面這個範例,就是TryFetchAsync會傳入網址和一個Action型別的委派( 稱為callback ),以功能面來說,就是抓取網頁資料的方法~~,然後這個方法定義了WebClient,準備抓取資料,那可想而知,WebClient一定會跑很久( 好啦,至少不會很快… ),所以如果沒使用非同步的話,就會需要等待;而這邊使用了非同步,所以會定義一個完成後的觸發事件,讓網頁下載完成後,在進入那個事件去做處理;也因此,在網頁下載的過程中,可以去做其他的事情,而不會卡在那邊~~。而這種感覺,就是小弟我前面說的,把等事情處理完後,再回來執行後面的程式碼。那當資料下載完後,就使用委派的方式,去呼叫傳進來Action型別的callback,如果成功就呼叫callback(args.Result,null),至於為什麼會這樣呼叫,是因為callback第一個型別為byte,第二個型別為exception,這兩個參數會由DownloadDataCompleted的事件處理,預設傳入的兩個參數來得知,由DownloadDataCompleted的事件處理的兩個參數為sender ( 事件的來源,也就是由誰觸發 )和args ( 事件的資料,看是成功後取得的資料,或是錯誤的資訊 )。好吧…有點離題太遠了,有興趣的可以參考msdn的這裡那裏

static void TryFetchAsync(string url, Action<byte[], Exception> callback)
{
    //使用WebClient抓取網頁資料
    var client = new WebClient();
    //定義事件,當下載完成後,執行此事件,_Sender、args是事件資料
    client.DownloadDataCompleted += (_, args) =>
    {
        if (args.Error == null) callback(args.Result, null);
        else if (args.Error is WebException) callback(null, null);
        else callback(null, args.Error);
    };
    //開始非同步方式下載
    client.DownloadDataAsync(new Uri(url));
}

當然,到了.Net 4.5開始,提供了async和await,讓使用者更加輕鬆的去處理,而且看起來和一般的Code沒啥差異,如下Code。( 一樣拿MSDN雜誌的Code~~因為小弟懶得再去想自己的Code了…請見諒阿… );基本上,功能和上面一樣,但是變得更加優雅了,我們加上了async並且用Task<byte[]>來當作回傳的型別,當WebClient抓到資料的時候,就直接回傳byte[],如果沒抓到,就回傳null。( 這邊小弟就不解說為什麼回傳用Task<byte[]>,但實際上,可以直接接到byte[]了,請見諒… )

static async Task<byte[]> TryFetchAsync(string url)
{
    //使用WebClient抓取網頁資料
    var client = new WebClient();
    try
    {
        return await client.DownloadDataTaskAsync(url);
    }
    catch (WebException) { }
    return null;
}

補充一下,當然,你也可以說,在沒使用async和await的情況下,我也可以直接把code塞到DownloadDataCompleted裡面去,的確,這樣的確也是可行,只要後續你要處理byte的程式碼不會很長…如果你後續還要對byte要做一堆的處理,那你想想看,我們要把這些東西塞到DownloadDataCompleted裡面去,不是就把整個cs檔案給塞爆了嗎…但不管怎樣,還是希望大家不要離題XDD,我們重點還是在討論async和await…其實中間的那段,可以省略不看啦0 0..

好,前面說了一堆大家都知道的東西,後續才是小弟比較重要的紀錄。

當我們使用async和await的時候,其實會產生類似如下的程式碼( 歹勢,請允許我說類似,因為底下的Code在C#下,可能是不能編譯的,但別忘了,編譯器編譯C#後,產生的是IL中間語言,恩,好吧,其實某方面也不是全對,底下的Code,IL也不能完全真的去跑,但這邊只是想用C#的語法來呈現概念,記這,這是概念~~ …P.S 同樣,Code來自MSDN 雜誌~ )

是的,底下其實是很長的,但我們分段來看吧,下面先po出全貌。

#if false // These are stubs of framework types shown in the paper

    public class AsyncTaskMethodBuilder<TResult>
    {
        public Task<TResult> Task { get; }
        public void SetResult(TResult result);
        public void SetException(Exception exception);
    }

    public struct TaskAwaiter<TResult>
    {
        public bool IsCompleted { get; }
        public void OnCompleted(Action continuation);
        public TResult GetResult();
    }
#endif

        static async Task<byte[]> TryFetchAsync(string url)
        {
            var client = new WebClient();
            try
            {
                return await client.DownloadDataTaskAsync(url);
            }
            catch (WebException) { }
            return null;
        }


    class TryFetch2
    {
        static Task<byte[]> TryFetchAsync(string url)
        {
            var __builder = new AsyncTaskMethodBuilder<byte[]>(); //建立生成器
            int __state = 0;
            Action __moveNext = null; //_moveNext委託 ( 播放 )
            TaskAwaiter<byte[]> __awaiter1;

            WebClient client = null;

            __moveNext = delegate
            {
                try
                {
                    if (__state == 1) goto Resume1;
                    client = new WebClient();
                Resume1:
                    try
                    {
                        if (__state == 1) goto Resume1a;
                        __awaiter1 = client.DownloadDataTaskAsync(url).GetAwaiter();
                        if (!__awaiter1.IsCompleted)
                        {
                        //__awaiter1未完成時,就先設定。
                            __state = 1;
                            __awaiter1.OnCompleted(__moveNext);//設定完程執行__moveNext (因為__moveNext是action型別,所以可以直接這樣寫)
                            return; //( 暫停 利用return跳出function,未來如果awaiter1完成,則會重新觸發__moveNext方法,再度進來。
                            //所以利用了這個機制,來達到"等待執行完"才會執行await後面的方法)
                        }
                    Resume1a://恢復執行
                        __builder.SetResult(__awaiter1.GetResult());//完成
                    }
                    catch (WebException) { }
                    __builder.SetResult(null);
                }
                catch (Exception exception)
                {
                    __builder.SetException(exception);
                }
            };

            __moveNext();//上面其實是在撰寫__moveNext的Function,這裡才是真正的第一次觸發__moveNext
            return __builder.Task;
        }
    }

首先,會建立兩個類別,分別是AsyncTaskMethodBuilder,這是一個生成器,到時候錯誤和Result都會利用SetException和SetResult來設定最終取得出來的結果 ( 也就是WebClient傳回來的結果 ) 並且放到Task裡面去,而最終,會利用Task來取得最終的結果。

第二個是TaskAwaiter,他會去定義一些方法,例如可以得知當WebClient是否完成,完成後要處理哪些事情、和取得完成後的資料。

#if false // These are stubs of framework types shown in the paper

    public class AsyncTaskMethodBuilder<TResult>
    {
        public Task<TResult> Task { get; }
        public void SetResult(TResult result);
        public void SetException(Exception exception);
    }

    public struct TaskAwaiter<TResult>
    {
        public bool IsCompleted { get; }
        public void OnCompleted(Action continuation);
        public TResult GetResult();
    }
#endif

接下來是重頭戲了,這邊有點攏長,請大家忍耐XDD;而這段Code也是真正後端處理的邏輯,他的步驟大致上如下。

  1. 會先建立一個AsyncTaskMethodBuilder這個類別,也就是建立生成器。
  2. 然後利用狀態機的概念,先把目前的狀態,設定為0,表示一開始。( int __state = 0; )
  3. 會建立一個新的Action委派,名稱為__moveNext。
  4. 準備好TaskAwaiter型別的變數,__awaiter1。
  5. 設定型別WebClient的變數為client ( 這應該是最簡單的一行吧0 0 )。
  6. 開始撰寫Action委派__moveNext裡面的內容 ( 實際上到這邊,也只是定義__moveNext會執行怎樣的東西罷了,不會真的去執行裡面的Code。 )
  7. 定義完後,跳到倒數第二行,也就是__moveNext();這裡。( 是的,到這邊,才真正的要開始執行步驟6定義的內容 )。
  8. 現在開始執行裡面的內容了,首先,先判斷__state是否為1 (也就是if(__state == 1) goto Resume1這行 ) ,理所當然,我們一開始的狀態__state = 0,所以就不會goto。
  9. 既然是第一次,所以這邊把WebClient給new起來了。
  10. 進入到第二個try裡面,同樣的,這邊判斷__state是否為1,如果為1,就跳到Resumela。
  11. 因為__state還是為0,所以這邊執行了要超長時間的client.DownloadDataTaskAsync,並且把取回的Awaiter,放到__awaiter1( 其實應該用指向啦,但用放到,初學者應該會比較好理解… )
  12. 接下來,用TaskAwaiter的IsCompleted來判斷,WebClient下載完了沒。
  13. 既然是要花超長時間下載,自然還沒好,所以又進入了if裡面。
  14. 而這時,就把狀態改成1了( __state = 1 ),雖然先把狀態改成1,但這時其實是還沒下載完成的喔!!
  15. 並且在__awaiter1.OnCompleted註冊了__moveNext這個方法。是的,沒有看錯,這邊代表當下載完成的時候,又會執行一次__moveNext這個方法一次,
  16. 然後就return了,表示完全離開了__moveNext()這個方法,跑去摸魚,喔,不是,跑去做其他的事情了。
  17. 當WebClient完成後,神奇的是其發生了,因為剛剛我們註冊了OnCompleted這個方法,所以當WebClient完成的時候,就觸發了__moveNext再執行一次!!
  18. 然後因為這次__state = 1了,所以就直接goto到Resume1,也不用再new一次WebClient了。
  19. 而又因為__state = 1,所以又跳到Resumela。
  20. 那Resumela那邊做了甚麼事情呢!?因為WebClient已經完成,就可以透過TaskAwaiter的GetResult取得資料,並且透過AsyncTaskMethodBuilder的SetResult,放到AsyncTaskMethodBuilder的Task裡面去。( 也就是__builder.SetResult(__awaiter1.GetResult());這行code )
  21. 最後,整個__moveNext()執行完後,就到了return __builder.Task,並且把Task回傳回去了~~
  22. 於是,整個TryFetchAsync真正的結束了~~

打完了整個步驟,感覺好像看完一部卡通一樣,有點哀傷的感覺(疑!?),但是從這個步驟中,我們就可以了解,整個底層的運作過程。

此外,過程中沒提到TaskAwaiter和Task的一些細節,這部分也請大家見諒,看看以後有機會,再把這兩個細節補完~~

class TryFetch2
    {
        static Task<byte[]> TryFetchAsync(string url)
        {
            var __builder = new AsyncTaskMethodBuilder<byte[]>(); //建立生成器
            int __state = 0;
            Action __moveNext = null; //_moveNext委託 ( 播放 )
            TaskAwaiter<byte[]> __awaiter1;

            WebClient client = null;

            __moveNext = delegate
            {
                try
                {
                    if (__state == 1) goto Resume1;
                    client = new WebClient();
                Resume1:
                    try
                    {
                        if (__state == 1) goto Resume1a;
                        __awaiter1 = client.DownloadDataTaskAsync(url).GetAwaiter();
                        if (!__awaiter1.IsCompleted)
                        {
                        //__awaiter1未完成時,就先設定。
                            __state = 1;
                            __awaiter1.OnCompleted(__moveNext);//設定完程執行__moveNext (因為__moveNext是action型別,所以可以直接這樣寫)
                            return; //( 暫停 利用return跳出function,未來如果awaiter1完成,則會重新觸發__moveNext方法,再度進來。
                            //所以利用了這個機制,來達到"等待執行完"才會執行await後面的方法)
                        }
                    Resume1a://恢復執行
                        __builder.SetResult(__awaiter1.GetResult());//完成
                    }
                    catch (WebException) { }
                    __builder.SetResult(null);
                }
                catch (Exception exception)
                {
                    __builder.SetException(exception);
                }
            };

            __moveNext();//上面其實是在撰寫__moveNext的Function,這裡才是真正的第一次觸發__moveNext
            return __builder.Task;
        }
    }

到這邊為止,大致上把整個async和await的底層補完,但大家有沒有想到一件事情!?是的,那就是Task.Yield勒!??我們引用一下MSDN雜誌的這句話。

靜態 Task.Yield 實用程序方法返回一個將聲稱(通過 IsCompleted)未完成的可等待操作,但會立即調度傳遞給它的 OnCompleted 方法的回調,就好像該操作實際已完成一樣。 這允許您強制進行調度並繞過編譯器為跳過它而進行的優化(如果結果已可用)。 您可以通過這種方式在「實時」代碼中擠出時間,同時提高並未處於空閒狀態的代碼的響應性。

簡單的說,Task.Yield會返回一個未完成的可等待操作(透過IsCompleted),但返回後,又會馬上調度OnCompleted,也因此,會立即的執行在await的下一行Code,就如同完成了await一樣,而這個狀況下,就可以在Code裡面,擠出一點時間( 雖然只是一下下,但對cpu來說,可能是很多了… )

另外,MSDN也提到Task.Yield的一個點。如下。

You can use await Task.Yield(); in an asynchronous method to force the method to complete asynchronously. If there is a current synchronization context (SynchronizationContext object), this will post the remainder of the method’s execution back to that context. However, the context will decide how to prioritize this work relative to other work that may be pending. The synchronization context that is present on a UI thread in most UI environments will often prioritize work posted to the context higher than input and rendering work. For this reason, do not rely on await Task.Yield(); to keep a UI responsive. For more information, see the entry Useful Abstractions Enabled with ContinueWith in the Parallel Programming with .NET blog.

大致上的翻譯是這樣的,您可以使用Task.Yield();來產生一個快速地中斷,來讓CPU有機會處理別的Thread,但如果當前有一個同步上下文(SynchronizationContext的對象),那Back回來後,這將繼續執行剩下的Code。然而,上下文會決定這項工作或相對於其他尚未了結工作的優先順序,而在大多數UI環境,上下文通常都會在目前的UI線程,所以往往會優先讓原本的Code高於UI輸入和UI渲染工作。出於這個原因,不要依賴於await Task.Yield(),以保持一個UI響應。

簡單的說,如果是想讓UI變快,用Task.Yield是沒用的,基本上Task.Yield還是用於提高其他邏輯上的處理…

後記

花了很長一段時間,把這些東西整理好,當然,對於整個await和async來說,這只是其中的一小部分,現階段小弟也沒力氣繼續追下去了,但相信Google也有更好的文章可以讓大家繼續研究下去,今天就先到這邊吧XDDD

參考資料

2013年7月18日

Visual Studio - 2013新功能

2013/07/18 更新

最近沸沸騰騰的其中一個項目大概就是VS 2013了,從出到現在,只有之前稍微看了一下版控的部分,IDE終於比較有時間可以玩一玩,所以就邊看教學影片邊紀錄~~,而整個看完後,才發現已經有滿多前輩朋友們,也都已經紀錄了很多文章..Orz…不過都已經截圖了…就當作是給自己的一個紀錄文吧~~

而每個功能,如果有發現有前輩有寫得不錯的文章,也會補充在下面,另外,每個功能的中文標題,都是小弟自己會意Key的,並沒有參考原文,請多見諒喔~~

首先,裝完Visual Studio後,第一個發現的應該就是出現了參考的連結。

參考

image

當我們點下去參考,VS2013就會很貼心的Show出相關的資訊!!

image

如果有三個參考,就會Show出三個=v=

image

而如果點了跳出來的資訊,就還可以看到是哪段Code參考到喔!!而且如果雙擊的話,還會直接導過去喔!!

image

好友小章哥也有介紹到這個功能~

Peek Definition (Alt + F12) 查看定義

另外,查看定義的功能也變強大了。

image

是的,它會自動地在原本的視窗再跳出來XDDD ( 超方便的啦~~ )

image

如果按下紅色框框,就會幫我們帶出大的視窗。

image

以上的功能Larry前輩有深入的介紹喔!!

接下來是顏色的設定,總之,就是多了vs2010的顏色…

image

同步功能

接下來是同步的功能,可以讓VS2013同步設定,這個對於常重灌的人,應該滿有用的XDDD

image

對這個功能有興趣的可以再參考Larry前輩的深入介紹!!

Enhanced Scrollbar 捲軸

接下來是旁邊的捲軸,我們只要在捲軸上按下滑鼠右鍵,就有選項可以選。

image

我們改成如下圖的垂直捲軸使用地圖模式。

image

那捲軸就會出現迷你版的Code,滑鼠一上去後,還可以看到放大的Code。其實這個功能VS2012的Power Tools就有了,現在終於內建進去了!!

image

不過小弟更喜歡這個,以前都裝Power Tools才有;這個功能就是會在捲軸上,用不同的顏色顯示位置,例如小弟這邊搜尋i,那旁邊的捲軸就會出現所有有i的顏色~~

image

這個功能Larry前輩Kevin哥都有介紹到~

與瀏覽器連結

這功能是當啟動偵錯的時候,會連結瀏覽器。

image

然後如果改了東西,就可以直接在VS2013裡面按下Ctrl + Alt + Enter 就會直接在瀏覽器上呈現改變,而不用去刷新瀏覽器…

image

另外,執行時期,也可以看出現在的JavaScript,並且設定中斷點。( 不過我想大家應該還是比較喜歡用瀏覽器偵錯吧0 0… )

image

偵錯時改變值

當我們針對進行偵錯,並設定中斷點的時候,當跑到那個中斷點的時候,是可以改變那行Code的,如下圖WPF的Code,原本要給的值是Test,現在在中斷點的時候,可以改成Test1111。

image

那只有WPF可以嗎??當然不是,ASP.NET MVC也行喔,我們設定return View();這行為中斷點。

image

如果是正常執行的話,應該會出現ASP.NET MVC 5的畫面。

image

但我們可以在中斷的時候改變,改成return Content(“cool”);

image

那輸出就會變成如下圖,當然Code改變後,就算停止偵錯,還是會維持Content(“Cool”),不會變回原本的View()喔!!

image

中斷的js也可以喔!!

image

Auto Brace Complete 自動產生結束括號

如下圖(此圖來自於Microsoft 官方Blog),這個功能也是Power Tools就有的,現在正是納入VS 2013裡面。當我們輸入"(" 的時候,會自動地出現右邊的括號")",不用我們在自己Key了~~( 不過小弟自己都有裝Power Tools,沒啥感覺就是了- -… )http://blogs.msdn.com/cfs-filesystemfile.ashx/__key/communityserver-blogs-components-weblogfiles/00-00-01-29-92/8228.bracecompletion.gif

New IDE Features for JavaScript 新的JS IDE功能

我們直接看下圖比較快,首先,比較明顯的是,上方出現了Function Bar,可以讓我們快速跳到想要的Function,另外,如果我們點了IDE編輯室窗裡面的Function ( 例如下圖的function( selector…. ) )則上方的Function也會自動跳喔!!

image

另外,我們可以看到,我們點集了某個變數之類的,在那個Function之下,就會高亮度出來。

image

更特別的是,他只會尋找Function底下,並不會亂七八糟的亂找,如下圖,只會顯示function底下的elem,上層的elem是不會高亮度的。

image

觀看function return values

這個功能也超級實用,可以觀看Function return後的值,如下的Code,我們可以從debug下,設定中斷點,來查看,不過這邊要查看,需要有點技巧,我們必須把中斷點設定在int result那行,然後中斷到那行的時候,在按F10(或是點選不進入函式)跳到"}"的地方,就可以從目前變數那邊看到資訊喔!!

其次也可以從即時運算視窗輸入$ReturnValue也可以看到,這算是一個很不錯的功能,尤其像小弟常常接一大串的情況下,這功能就很好用~~ ( 微軟Blog說明 )

image

但如果回傳的是物件之類的,則會顯示如下圖,基本上還是可以看的到回傳的東西就是了~~

image

除此之外,還有小弟沒發現,但Larry前輩有玩出來的一些功能,例如可以程式碼分析的加強整行的拖動功能選項的尋覽內嵌巡覽等等,有興趣的可以去看看喔!!

後記

除了這幾項以外,還有幾個大功能,因為功能比較大,就不列在這篇裡面了,未來有機會,再來繼續補充完畢!~

參考網址

TFS - 新的Features Item

持續來看看TFS的新功能項目之一,如果有再跑Scrum之類的,可能會常常進入到Item的管理介面裡面去,而這次TFS 2013和Team Foundation Service多出了一個Features的Item。

如下圖所示,我們可以看到Features出現在左邊,而我們可以新增一個Features。目前小弟自己的看法,認為Features是在大於Product Backlog層級的Item,例如某些產品要那些特色,而這些特色裡面會在包含了Product Backlog的需求,然後再去細分為實作的Sprint Backlog( Task ),這樣分層的好處,就是可以更加得清楚,使用者到底要的是怎樣的東西。

image

而如果真的要建立的話,我們也可以發現,和之前的Product Backlog有所不同,尤其是Target Date這項,我們可以定義一個目標日期;至於為什麼會有這個選項,猜想因該也是為了讓整個專案能更加的關聯住,但小弟目前還沒看到官方提供正式的說明,所以就先不加以猜測了。

image

其次,如果點選了第一張圖箭頭的位置,還可以切換想要關注的觀點;比方說,小弟切換成Features to Backlog items時,我們就可以再從Features前面的加號,新增一個新的Product Backlog…從這邊就可以看到階層的關係。

image

最後,我們也發現Backlog Items的選項旁邊多了圖示可以點選,我們點下去之後,就可以看到Product Backlog的階層,老實說,這真的是個很方便的功能。

image

另外,我們也可以從下圖的Mapping功能,開啟右邊的側欄,就可以發現已經建立好的Features,我們也可以直接把Product Backlog拖拉過去,那這樣,就會被歸類到Features底下。

image

大致上到這邊。

後記

有些應用官方還沒跳出來解說該如何用( 又或是小弟沒找到,沒時間看QQ ),所以在這邊先稍微介紹一下,未來如果有知道怎樣是官方建議的應用,會在修改此文章,給大家參考,也請大家多見諒。

2013年7月17日

TFS - 在Web管理介面的Code增加說明與討論

這也是屬於TFS 2013和Team Foundation Service的新功能之一,以前我們要針對Code進行討論等等( 不是那種雙斜線的註解喔 ),我們會用Code Review的功能,但是Code Review通常是使用在未簽入之前,而且使用者可以自己選擇要給誰Review,所以並不是每個人都可以收到Review的通知。

image

另外一種情況,我們可能簽入後,才會看到Code,而也有可能會對於簽入後的Code,有些想要說明,或是討論的,所以現在新的版本,更加的強化了這個部分。

我們可以從Web的管理介面,的Code的地方進去,我們就可以針對想要了解註解的功能進行註解。

image

完成後就會如下這樣,而其他人也可以在下面回話與討論。

image

這是屬於TFS的新功能之一,雖然目前還沒有很多的應用,但相信未來官方會再針對這塊有更詳細的解說。

2013年7月15日

SQL Server - FileTable拒絕存取之問題

最近,連續發了幾篇SQL Server的文章,這大概也是有史以來連續最多的一次…Orz…,回歸正題,不知道大家還記不記得之前寫的FileTable文章,經過一段時間的使用,我們終於把他佈署到半正式的環境了!!

然後就發生了錯誤…Orz…但這次發生的問題一開始還覺得滿詭異的,因為使用Web Api,所以只吐回來發生錯誤…,重點是,Local開的IIS Express是正常的,完全不知道哪裡錯,後來想辦法把問題調閱出來,才發現這次發生的問題,如下圖,答案就是"拒絕存取",而詳細的錯誤訊息如下,不過大家約略看看就可以跳過了,不是重點= =+。

image

"Message":"發生錯誤。","ExceptionMessage":"類型 'MultipartFormDataStreamProvider' 的資料流提供者擲回例外狀況。","ExceptionType":"System.InvalidOperationException","StackTrace":" 於 System.Net.Http.MimeBodyPart.GetOutputStream(HttpContent parent)\r\n 於 System.Net.Http.HttpContentMultipartExtensions.MultipartWriteSegmentAsync(MultipartAsyncContext context)\r\n 於 System.Net.Http.HttpContentMultipartExtensions.MoveNextSegment(MultipartAsyncContext context)\r\n--- 先前擲回例外狀況之位置中的堆疊追蹤結尾 ---\r\n 於 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n 於 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n 於 System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\r\n 於 CSW.Controllers.API.ActivityImgController.<Post>d__0.MoveNext()","InnerException":{"Message":"發生錯誤。","ExceptionMessage":"拒絕存取路徑 '\\\\localhost\\sqldb\\FileTable\\Activities\\BodyPart_4329878c-2610-4f2d-ba16-f5ce595f603f'。","ExceptionType":"System.UnauthorizedAccessException","StackTrace":" 於 System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)\r\n 於 System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)\r\n 於 System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy)\r\n 於 System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options)\r\n 於 System.Net.Http.MultipartFileStreamProvider.GetStream(HttpContent parent, HttpContentHeaders headers)\r\n 於 System.Net.Http.MimeBodyPart.GetOutputStream(HttpContent parent)"}}

好的,我們回頭看一下這次的問題,這次的問題是"拒絕存取",但Local可以正常運作,那為何佈署上去後,就不行了呢??,我想聰明的大家,應該都已經想到了!?

是的,既然問題是"拒絕存取",那就表示是因為權限不足,那為何Local又可以執行呢??( 總覺得鋪梗鋪得很像藍色蜘蛛網- -… ),答案很簡單,因為小弟執行Visual Studio是使用Administrator的權限執行,所以IIS Express也是Administrator的權限…那存取FileTable的資料夾,自然也暢行無阻。

但在現實的環境中,就沒那麼單純了,在正式的Server上,IIS並不會有最高的權限執行…( 因該也不會有人想用最高的權限執行吧… ),所以自然而然,就變得沒有權限去存取FileTable的資料夾…( 怕有人誤會,所以補充一下,這邊是代表,沒有權限使用Windows IO機制來直接存取FileTable的檔案,也就是說,沒有權限使用檔案總管來新增檔案,而不是在敘說寫入到db的權限 )

既然知道問題了,那該怎麼做呢!?,基本上,小弟也不太會XDDD,所以今天搬出了公司所有SQL Team的救兵,最後動用到許致學老師才幫忙解決…( 深深感謝大家QQ~~ )

首先,雖然權限的問題,在一般的Web應用也會碰到,但FileTable的設定權限是沒辦法從檔案總管那邊下手去設定的,因為FileTable的權限會透過SQL Server來控管,所以我們要透過SSMS來針對SQL Server的權限設定下手,簡單的說,就是把IIS的權限加到SQL Server裡面去。

首先,我們先從登入的地方,來新增一個新的登入,畢竟,要透過SQL Server,那一定要先有登入帳號阿!!,不然連SQL Server都沒登入的權限,那還有啥權限可以去讀取FileTable…

所以我們可以如下圖,按下搜尋,來把IIS的Windows帳號加入。

image

但有那麼單純嗎?當然沒有,因為新版的IIS帳號,已經變成虛擬帳號了,對於虛擬帳號有興趣的朋友們,可以參考保哥這篇文章;所以我們要加入到SQL Server裡面的是一個IIS的虛擬帳號,那帳號名稱到底是甚麼呢?就要開啟IIS來看一下,目前我們到底用到的是哪個應用程式集區;由下圖可以發現,我們用到的是ASP.NET v4.0。( IIS的虛擬帳號,會隨著應用程式集區的不同,而有不同的名稱。 )

image

所以我們回到SQL Server,就可以選擇剛剛找到的虛擬帳號,而IIS的虛擬帳號,預設是IIS AppPool\{應用程式集區的名稱},所以我們上面是ASP.NET v4.0,所以這邊要輸入IIS AppPool\ASP.NET v4.0,輸入完成後,就可以檢查名稱一下,如果無誤,就可以發現,變成了有底線的ASP.NET v4.0。

image

那真的那麼簡單嗎??當然不是那麼簡單,如果這樣就OK了,小弟也不用寫這篇文章記錄了,雖然我們上面輸入了正確的帳號名稱,但當我們按下確定的時候,SSMS又會很"聰明"的幫我們加上主機名稱…變成"主機名稱\ASP.NET v4.0",也因此,這個帳號還是不對的,所以我們還是必須手動的回到如下圖的地方,輸入正確的虛擬帳號…( 所以上圖的那個步驟,其實是可以不用做的…只是想提醒大家,因為SSMS太聰明了,所以造成了我們的困擾- -+ )

image

輸入完成後,我們到伺服器角色的地方,選擇public就可以了,也不用太多的權限。

image

接下來,使用者對應這邊,我們要讓剛剛輸入好的SQL Server登入帳號對應到有FileTable的資料庫,簡單的說,就是資料庫會對應剛剛我們建立好的SQL Server登入帳號,而這個登入帳號又會是Windows的IIS虛擬帳號… ( 真多帳號阿- - ),如下圖,印象中寫入和讀取的權限可以不用勾,但有點忘記了,如果做完還是不行的話,可以考慮再回來勾一下。( 歹勢,中間測試太多東西… ),然後就可以按下確定來建立SQL Server登入帳號,並且和資料庫有關聯了。

image

接下來,我們到FileTable的地方,並且選擇權限,我們就可以透過搜尋找到剛剛建立的SQL Server登入帳號,那這邊我給的權限也是刪除、更新、插入、選取,理論上改變應該是可以不用勾選,同樣的,如果真的不行,大家還是可以回來勾勾看。

image

這邊是插入和選取的地方。

image

完成之後,終於就可以順利的寫入進去了( 淚 )。

image

這樣,就完成了權限的設定~~

後記

最後,還是要感謝各位同事的支援,與老師的協助~~也花了大家非常多的時間…重點是,到最後帳號其實都ok,但小弟程式有誤,所以還以為不行,在那邊繞圈子…真是對不起阿~~ ( 遮臉.. ),總之,造成大家困擾了,在這邊感謝大家協助,也希望這篇文章能協助到有遇到相同困難的朋友~~

SQL Server - 更改FileTable的Windows共用名稱

這篇也是小小的一篇,但當下,小弟竟然完全不知道該如何去改,所以這邊也記錄一下,不然到時候可能又會忘記…

還記得前篇,我們介紹了FileTable的開啟與使用,而當時的路徑是設定為xxx\sqlexpress\FileTest,如下圖,但我們Team在開發的時候,因為有些人是使用SQL Server為開發主體,取名為sqlexpress就會讓人感覺怪怪的… ( 好啦,我龜毛啦!!~ ),所以要改一下現在的名稱。

image

我們期許變成如下圖…而當初改的時候,一直遇到一些小問題( 現在感覺出來真的很小 ),所以就在這邊紀錄一下。

image

要改的方式滿簡單的,我們只要開啟Sql Server Configuration Manager後再選擇SQL Server的服務,然後就可以去做調整,如下圖。

image

接下來就可以看到Windows共用名稱 ( 下圖是已經改好的 ),原本以為只要直接把Windows 共用名稱改成想要的字串就可以,但結果…

image

是的,會出現以下錯誤。

只有在FILESTREAM已啟用檔案I/O存取時才能變更FILESTREAM共用。您應該停用FILESTREAM檔案I/O存取,再以新的共用名稱重新啟動。(0x80041016)。

老實說,小弟當下讀了幾次,還真是不知道該如何解決… ( 國文老師對不起,國文沒學好… )

image

後來才領悟,原來是要這樣做。( 這時候有種領悟武功祕笈的感覺= = )。

首先,要先把啟用FILESTREAM的檔案I/O存取的chickbox取消勾選,然後按下套用。

image

然後再把勾選勾起來,並且改Windows 共用名稱…然後再套用…

image

是的,就是那麼簡單那麼簡單那麼簡單那麼簡單那麼簡單那麼簡單…Orz…

這樣名稱就可以改過來了…( 搞了半天- - )…