顯示具有 程式寫作 標籤的文章。 顯示所有文章
顯示具有 程式寫作 標籤的文章。 顯示所有文章

2013年12月27日 星期五

[W8App][C#] Task Cancellation

若想要讓產生的Task具有可取消的機制,則必需透過以下static methods取得task handle與傳入cancel token來達成
Task.Run(Action, CancellationToken)
Task.Run(Func<Task>, CancellationToken)
Task.Run<TResult>(Func<Task<TResult>>, CancellationToken)
Task.Run<TResult>(Func<TResult>, CancellationToken)

這個範例是我設計的一個對話盒控制項,當對話盒產生時呼叫InitItemsAsync以非同步的方式產生圖片對載入UI的list控制項中,使用者可以不必等待所有的圖片都產生即按下OK(或Cancel)按來關閉這個對話盒。因此我必需讓InitItemsAsync具有可被取消的機制。

   1: public sealed partial class MyDialog : UserControl
   2: {
   3:     private CancellationTokenSource _cts;
   4:     private CancellationToken _token; 
   5:     private Task _opInit;

#3-5 我們的物件必需儲存CancellationTokenSource、CancellationToken與Task方便在動作的對應函式中取消



   1: private async Task InitItemsAsync()
   2: {
   3:     for(int i=0 ; i<24 ; i++)
   4:     {
   5:         if (_token.IsCancellationRequested) break;
   6:  
   7:         await LoadImageAsync(i);
   8:     }
   9: }

#5 非同步的InitItemsAsync函式,當_token收到取消的要求時(IsCancellationRequested)提前結束迴圈



   1: _cts = new CancellationTokenSource();
   2: _token = _cts.Token;
   3: _opInit = Task.Run(async () =>
   4: {
   5:     await InitItemsAsync();
   6: },
   7: _token);

透過Task.Run來執行非同步的函式,並載入cancel token。
#5 InitItemAsync是非同步的函式,因此必需有async / await來載明


PS: 我嘗試過不用Lambda的寫法,而是直接以函式指標的方式來使用Task.Run,但在VS2013編譯時期會有函式型別模糊(error CS0121: The call is ambiguous between the following methods or properties)的錯誤,目前還不知道解決的方式。



   1: private bool TryCancelInitItems()
   2: {
   3:     if (_opInit.IsCompleted == false)
   4:     {
   5:         try
   6:         {
   7:             _cts.Cancel();
   8:  
   9:             _opInit.Wait(_token);
  10:  
  11:             return true;
  12:         }
  13:         catch(System.OperationCanceledException)
  14:         {
  15:             return true;
  16:         }
  17:         catch (Exception e)
  18:         {
  19:             Debug.WriteLine(@"{0}", e);
  20:  
  21:             return false;
  22:         }
  23:     }
  24:  
  25:     return true;
  26: }
當使用者按下結束對話盒的按鈕時,呼叫我的TryCancelInitItems函式來取消InitItemsAsync。

#3 先檢查task是否已經完成,若尚未完成才需使用取消機制
#7 呼叫取消並等待_token
#13 若InitItemsAsync被取消了,會丟出System.OperationCanceledException

2013年10月25日 星期五

[W8App] 8.0 “Snapped” to 8.1– with LayoutAwarePage

Windows 8.1的一個德政是取消了原本的Snappped View限制,App除了FullScreen外還可以有任意的大小(只是有最小限制)。不過對於已經開發好的8.0 App,就要花時間重新調整不同視窗大小時Layout。

我的懶人改法是,除了FullScreen外一律視為Snapped。依這樣的邏輯其實只要改動一個function – LayoutAwarePage的DetermineVisualState:

   1: protected virtual string DetermineVisualState(ApplicationView appView)
   2: {
   3:     if(appView.IsFullScreen)
   4:     {
   5:         return @"FullScreen" + appView.Orientation.ToString();
   6:     }
   7:     else
   8:     {
   9:         return @"Snapped";
  10:     }
  11: }

1. 原本參數為ApplicationViewState (編譯時有warning),改為ApplicationView
3-10. 原本view state可以直接吐回字串的部份用邏輯修正


當然呼叫端也要調整為



   1: string visualState = DetermineVisualState(ApplicationView.GetForCurrentView());
1.  原本呼叫端是傳入ApplicationView.Value (編譯時有warning),改為ApplicationView.GetForCurrentView()

2013年10月14日 星期一

[W8App] D2DERR_WRONG_FACTORY (0x88990012)

最近在學習W8App的Direct2D與DirectWrite時發生了一個例外的錯誤,當呼叫D2D的EndDraw來更新繪圖結果是產生了0x88990012 (D2DERR_WRONG_FACTORY)的例外錯誤。

這個錯誤發生的原因是我的DirectWrite Render物件預先儲存了D2D factory,而透過SurfaceImageSource來連結W8App與Direct2D時,D2DContext所使用的D2D factory似乎有自已獨立的產生方式。

目前的解決方式是透過GetFactory直接從D2DContext物件中取得D2DFactory,至於是否有更好的作法,就需要再研究。

   1: IFACEMETHODIMP CustomTextRenderer::DrawGlyphRun(
   2:     _In_opt_ void* clientDrawingContext,
   3:     FLOAT baselineOriginX,
   4:     FLOAT baselineOriginY,
   5:     DWRITE_MEASURING_MODE measuringMode,
   6:     _In_ DWRITE_GLYPH_RUN const* glyphRun,
   7:     _In_ DWRITE_GLYPH_RUN_DESCRIPTION const* glyphRunDescription,
   8:     IUnknown* clientDrawingEffect
   9:     )
  10: {
  11:     HRESULT hr = S_OK;
  12:  
  13:     Microsoft::WRL::ComPtr<ID2D1Factory> d2dFactory;
  14:     m_d2dContext->GetFactory(&d2dFactory);
  15:  
  16:     // Create the path geometry.
  17:     Microsoft::WRL::ComPtr<ID2D1PathGeometry> pathGeometry;
  18:     hr = d2dFactory->CreatePathGeometry(&pathGeometry);
  19:  
  20:     // Write to the path geometry using the geometry sink.
  21:  
  22:     Microsoft::WRL::ComPtr<ID2D1GeometrySink> sink;
  23:     if (SUCCEEDED(hr))
  24:     {
  25:         hr = pathGeometry->Open(&sink);
  26:     }
  27:  
  28:     // Get the glyph run outline geometries back from DirectWrite and place them within the
  29:     // geometry sink.
  30:     if (SUCCEEDED(hr))
  31:     {
  32:         hr = glyphRun->fontFace->GetGlyphRunOutline(
  33:             glyphRun->fontEmSize,
  34:             glyphRun->glyphIndices,
  35:             glyphRun->glyphAdvances,
  36:             glyphRun->glyphOffsets,
  37:             glyphRun->glyphCount,
  38:             glyphRun->isSideways,
  39:             glyphRun->bidiLevel %2,
  40:             sink.Get()
  41:             );
  42:     }
  43:  
  44:     // Close the geometry sink
  45:     if (SUCCEEDED(hr))
  46:     {
  47:         hr = sink.Get()->Close();
  48:     }
  49:  
  50:     // Initialize a matrix to translate the origin of the glyph run.
  51:     D2D1::Matrix3x2F const matrix = D2D1::Matrix3x2F(
  52:         1.0f, 0.0f,
  53:         0.0f, 1.0f,
  54:         baselineOriginX, baselineOriginY
  55:         );
  56:  
  57:     // Create the transformed geometry
  58:     Microsoft::WRL::ComPtr<ID2D1TransformedGeometry> transformedGeometry;
  59:     if (SUCCEEDED(hr))
  60:     {
  61:         hr = d2dFactory->CreateTransformedGeometry(
  62:             pathGeometry.Get(),
  63:             &matrix,
  64:             &transformedGeometry
  65:             );
  66:     }
  67:  
  68:     // Draw the outline of the glyph run
  69:     if(SUCCEEDED(hr))
  70:     {
  71:         m_d2dContext->DrawGeometry(transformedGeometry.Get(),m_brushOutline.Get());
  72:         m_d2dContext->FillGeometry(transformedGeometry.Get(),m_brushFill.Get());
  73:     }
  74:  
  75:     return hr;
  76: }

13-14 透過目前的D2DContext來取得D2DFactory

2013年6月1日 星期六

[W8App] ListBox Binding data (5) – notify count changed

要如何得知ListBox繫結的資料數量有變化呢?由於資料來源是透過繫結,所以要得知資料量有有更動時必需加入聆聽ListBox.Items.VectorChanged事件。

   1: myListBox.Items.VectorChanged += VectorChangedEventHandler;


當數量改變時,會呼叫指定的VectorChangedEventHander函式:



   1: public void VectorChangedEventHandler(IObservableVector<object> sender, IVectorChangedEventArgs e)
   2: {
   3:     myListBox.Visibility = (sender.Count > 0) ? Visibility.Visible : Visibility.Collapsed;
   4:  
   5:     String strValue = String.Format(@"({0})", sender.Count);
   6:  
   7:     Debug.WriteLine(strValue);
   8: }

2013年5月29日 星期三

[W8App] ListBox Binding data (4) – Update data asynchronous

W8App的非同步(Async)與傳統桌面系統的多緒(multi-thread)在操作上有一點點的不太一樣,傳統的多緒系統CreateThread通常都會對應一個WaitForSingleObject,等待該工作的完成。但非同步的機制確不需要再額外coding一個等待機制隨時的檢查資料是否有更新(或是檢查資料是否全部更新完)

Async

以ListBox為例,我們可以把與ListBox繫結的資料,以非同步的方式一筆筆的加到ObservableCollection集合中。這對於大量或是遠端的資料的使用上可避免UI長時間的占用請形有很大的幫助。

不過當ListBox繫結非同步資料時有一點必需注意,新增與移除資料必需是在UI元件同樣的執行緒上執行,

   1: await Window.Current.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
   2: {
   3:     // read items collection from file or network ...
   4:     foreach (var item in items)
   5:     {
   6:         SampleDataGroup group = SampleDataSource.GetGroup("AllGroups");
   7:         group.Items.Add(new SampleDataItem("","","","","","",group));
   8:     }
   9: });

Window.Current會取得目前的window,但是要注意的是若新增(移除)的非同步機制是在另一個頁面或是Runtime dll中時,可能會取到不正確的window,這時就必需先記錄ListBox所在的windows資料。


參考資料:

1. Async Made Simple with C++ PPL, Rahul V. Patil, Microsoft Corporation

2013年5月27日 星期一

[筆記] XmlElement can not call SetAttribute in foreach?

C#對我而言只是會用而已,有些語法還不是很了解,今天遇到一個問題不清楚是C#的限制還是Bug?

我使用XmlDocument讀取xml文件,並檢查文件中的Bookmark元素們的Bookmarkname屬性,當屬性不存在或為空字元(“”)時視為舊版本,從Filename屬性讀出資料更新Bookmarkname屬性。

原本是使用C#的foreach來檢查每個Bookmark元素

   1: var elements = _xmlProject.GetElementsByTagName("Bookmark");
   2: foreach(var element in elements)
   3: {
   4:     String bookmarkname = element.GetAttribute(@"Bookmarkname");
   5:     if (bookmarkname == null || bookmarkname == "")
   6:     {
   7:         bookmarkname = element.GetAttribute(@"Filename");
   8:         element.SetAttribute(@"Bookmarkname", bookmarkname);
   9:     }
  10: }

不過當發現元素為舊版格式不存在Bookmarkname屬性而呼叫SetAttribute後,下一筆element讀取時一定會發生Invalidate exception,若改為for語法就一切正常,更改後為



   1: var elements = _xmlProject.GetElementsByTagName("Bookmark");
   2: int count = elements.Count;
   3: for(int i=0 ; i<count ; i++)
   4: {
   5:     var element = elements[i] as XmlElement;
   6:  
   7:     String bookmarkname = element.GetAttribute(@"Bookmarkname");
   8:     if (bookmarkname == null || bookmarkname == "")
   9:     {
  10:         bookmarkname = element.GetAttribute(@"Filename");
  11:         element.SetAttribute(@"Bookmarkname", bookmarkname);
  12:     }
  13: }

所以是C#的foreach並不是list的形式所以資料集合不能有記憶體配置的變化?還只是單純的bug?