星期二, 6月 23, 2020

[SQL] Indexed View

根據 建立索引檢視(Indexed View)來提高查詢效能 內的 TSQL 範例來學習,利用 AdventrueWorks2017 的 Sales.SalesOrderHeader 和 Sales.SalesOrderDetail 來跑彙總統計

未建立 Indexed View 前的 TSQL 執行計畫
-- 建立 IX_SalesOrderDetail_ProductID,讓執行計畫單純一些
CREATE INDEX IX_SalesOrderDetail_ProductID ON Sales.SalesOrderDetail (ProductID)
INCLUDE (OrderQty, UnitPrice, UnitPriceDiscount)
WITH (DROP_EXISTING = ON)

SELECT 
    o.OrderDate , 
    od.ProductID ,
    SUM(od.UnitPrice * od.OrderQty * (1.00 - od.UnitPriceDiscount)) AS Revenue
FROM Sales.SalesOrderHeader AS o 
    JOIN Sales.SalesOrderDetail AS od ON o.SalesOrderID = od.SalesOrderID
WHERE od.ProductID BETWEEN 700 and 800
    AND o.OrderDate >= '20130501'
GROUP BY o.OrderDate, od.ProductID
ORDER BY Revenue DESC;
GO
[SQL] Indexed View-1

星期一, 6月 22, 2020

[SQL] 執行計畫成本

討論時常提到 SQL Server 會選擇執行計畫成本較低的來跑,但其實會影響因素很多,本篇筆記以 [資料量] 和 [Key Lookup] 兩者來驗證,抓取過多資料和非叢集索引無法滿足所需資料,而必須透過 key lookup 抓取,兩者都是觀察執行計畫重點

在 AdvenureWork Sales.SalesOrderHeader Table 建立 IX_SalesOrderHeader_OrderDate 來記錄
use AdventureWorks2017
go 

CREATE INDEX IX_SalesOrderHeader_OrderDate ON Sales.SalesOrderHeader (OrderDate)
WITH (DROP_EXISTING = ON);
case1:以單日 (20130701) 資料來觀察執行計畫
SELECT 
    SalesOrderNumber , 
    OrderDate ,
    CustomerID ,
    SubTotal
FROM Sales.SalesOrderHeader
WHERE OrderDate BETWEEN '20130701' AND '20130701'
IX_SalesOrderHeader_OrderDate Index Seek 並輸出 43 筆資料後,利用 key lookup 取回 CustomerID 和 SubTotal 資料
case2:以單月 (201307) 資料來觀察執行計畫
SELECT 
    SalesOrderNumber , 
    OrderDate ,
    CustomerID ,
    SubTotal
FROM Sales.SalesOrderHeader
WHERE OrderDate BETWEEN '20130701' AND '20130731'
PK_SalesOrderHeader_SalesOrderID Index Scan 並輸出 31,465 筆資料後,最後透過 Filter 來篩選資料至 1,740 筆
當日期區間拉長 (單日 => 單月,資料量增加) 時,SQL Server 選擇執行計畫成本較較低的 Clustered Index Scan (PK_SalesOrderHeader_SalesOrderID) + Filter 來跑

接下來透過 case3:以單月 (201307) 並強制跑 IX_SalesOrderHeader_OrderDate 來驗證 SQL Server 會選擇成本較低的執行計畫來跑
SELECT 
    SalesOrderNumber , 
    OrderDate ,
    CustomerID ,
    SubTotal
FROM Sales.SalesOrderHeader WITH (INDEX(IX_SalesOrderHeader_OrderDate))
WHERE OrderDate BETWEEN '20130701' AND '20130731'
因為有 Index Hint,所以用 IX_SalesOrderHeader_OrderDate 並輸出 1,740 筆資料後,利用 key lookup 取回 CustomerID 和 SubTotal 資料
把 case2 和 case3 TSQL 放在一起跑,就可以很明顯比較出執行計畫成本差異

星期六, 6月 20, 2020

[X.Form] ResizetizerNT

Cross-platform Images Simplified with ResizetizerNT 介紹才知道的開源套件,基本上就是把 Image 轉為各平台定義格式,設定和使用都很簡易

從 nuget 上安裝 ResizetizerNT

Resizetizer NT-1

Share Project 內放置一張圖片,文件上是說 SVG 或 PNG 圖片都可以,但影片建議是用 SVG,並把該圖片的 Build Action 設定為 SharedImage

Resizetizer NT-2

Project Property 內設定圖片 BaseSize,基本上這就是最小 Size
<Project Sdk="Microsoft.NET.Sdk">
  <ItemGroup>
    <SharedImage Include="Image\Xamarin.svg" BaseSize="60,60"/>
  </ItemGroup>
</Project>
對 project 進行 build 之後,各平台內就可以發現有 Resizetizer 資料夾,資料夾內可以找到不同 Size 的 png 檔案

Resizetizer NT-4

Resizetizer NT-5

寫個簡單 xaml 並把 app 部屬至模擬器來看看效果囉
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             x:Class="ResizetizerDemo.MainPage">

    <StackLayout>
        <Label Text="Welcome to Resizetizer NT!" 
           HorizontalOptions="Center"
           VerticalOptions="Center" 
           FontSize="Large"
           FontAttributes="Bold"/>
        <Image Source="Xamarin.png" 
               WidthRequest="360" 
               HeightRequest="360" 
               VerticalOptions="CenterAndExpand" />
    </StackLayout>

</ContentPage>
Resizetizer NT-7

星期日, 6月 14, 2020

[X.Form] 呼叫 WebApi 顯示圖片

[WebApi] 下載圖片 完成 WebApi 建置後,該篇要利用 Xamarin 來呼叫 WebApi 並顯示圖片

Xaml 配置
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             x:Class="ImageDemo.MainPage">

    <StackLayout>

        <Label Text="透過 WebAPI 連線本機 IIS 並顯示圖片" 
               FontSize="Large"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <Button 
                x:Name="BtnClick"
                Text="run"
                FontSize="Large"
                BorderWidth="1"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand"
                Clicked="Button_Clicked"></Button>
        
        <Image
            x:Name="img" 
            Aspect="AspectFit"
            WidthRequest = "480"
            HeightRequest = "640"/>
    </StackLayout>
</ContentPage>
C# Code
namespace ImageDemo
{
    [DesignTimeVisible(false)]
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }

        private async void Button_Clicked(object sender, EventArgs e)
        {
            byte[] imageByte = await DownloadImageAsync("http://10.0.2.2:8081/api/image?file=3");
            img.Source = ImageSource.FromStream(() => new MemoryStream(imageByte));
        }

        private async Task<byte[]> DownloadImageAsync(string imageUrl)
        {
            byte[] imageByte;
            using (HttpClientHandler handler = new HttpClientHandler())
            using (HttpClient client = new HttpClient(handler))
            {
                try
                {
                    #region 呼叫遠端 Web API
                    HttpResponseMessage response = null;

                    // 設定相關網址內容
                    response = await client.GetAsync(imageUrl);

                    #endregion

                    #region 處理呼叫完成 Web API 之後的回報結果
                    if (response != null)
                    {
                        if (response.IsSuccessStatusCode == true)
                        {
                            // 重點:Content 利用 ReadAsByteArrayAsync() 取回圖檔 byte
                            return await response.Content.ReadAsByteArrayAsync();
                        }
                        else
                        {
                            imageByte = null;
                        }
                    }
                    else
                    {
                        imageByte = null;
                    }
                    #endregion
                }
                catch
                {
                    imageByte = null;
                }
            }

            return imageByte;
        }        
    }
}
從 iOS 模擬器和 Android 模擬器連線到本機 web 服務 重點
在 Android 模擬器中執行的應用程式可以透過 10.0.2.2 位址連線到本機 HTTP Web 服務,也就是您的主機回送介面 (在開發電腦上為 127.0.0.1) 的別名。 例如,提供一個透過 /api/todoitems/ 相對 URI 公開 GET 作業的本機 HTTP Web 服務,在 Android 模擬器中執行的應用程式就可以透過傳送 GET 要求至 http://10.0.2.2:/api/todoitems/ 來取用作業。
執行結果
[X.Form] 呼叫 WebApi 下載圖片


星期六, 6月 13, 2020

[WebApi] 下載圖片

透過 WebApi 來傳輸圖檔

WebApi Code
namespace ImageDemo.Controllers
{
    public class ImageController : ApiController
    {
        [HttpGet]
        [Route("api/image")]
        public IHttpActionResult Get(string file)
        {
            #region 取得server的相對路徑

            // 指定 IIS 所在 Server 的資料夾位置
            string sourceDir = $"D:/WebAPIDemoPhoto";
            if (Directory.Exists(sourceDir) == false)
            {
                return BadRequest("資料夾不存在");
            }

            // 限定 jpg 檔案
            var fileFullName = Directory.GetFiles(sourceDir, "*.jpg")
                .FirstOrDefault(f => Path.GetFileNameWithoutExtension(f) == file);
            if (fileFullName == null)
            {
                return BadRequest("檔案不存在");
            }

            #endregion

            // jpg 圖檔為 image/jpeg
            var mimeType = MimeMapping.GetMimeMapping(fileFullName);

            var response = new HttpResponseMessage(HttpStatusCode.OK);
            var fileStream = new FileStream(fileFullName, FileMode.Open, FileAccess.Read);
            response.Content = new StreamContent(fileStream);
            response.Content.Headers.ContentType = new MediaTypeHeaderValue(mimeType);
            response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
            {
                FileName = HttpUtility.UrlPathEncode(file)
            };
            // 告知瀏覽器下載長度
            response.Content.Headers.ContentLength = fileStream.Length;

            return ResponseMessage(response);
        }
    }
}
部屬至本機 IIS 上後,利用 Postman 來打看看

Header 資訊

[WebApi] 下載圖片-1

沒想到 Postman 內可以顯示該圖片

[WebApi] 下載圖片-2

星期三, 6月 10, 2020

[X.Form] HttpClient 抓取 API 資料

課程上老師抓取 [開放資料平台 - 特生中心蛾調志工蛾類調查資料集],來介紹 HttpClient 抓取 API 資料,把該範例改成抓 json 來練習

透過該 網站,把抓下來的 json 格式化,方便閱讀,json 資料格式如下圖,records 是資料

[X.Form] HttpClient 抓取 API 資料-2

操作畫面呈現

[X.Form] HttpClient 抓取 API 資料-1

星期二, 6月 09, 2020

[VS] 選擇性貼上

開放資料平台上抓到 json 後,利用 VS 功能 - 選擇性貼上,來完成 Model 建置

編輯 => 選擇性貼上 => 貼上 json 作為類別

[VS] 選擇性貼上-1

產生的 Model
namespace ConsoleApp2
{
    public class Rootobject
    {
        public bool success { get; set; }
        public Result result { get; set; }
    }

    public class Result
    {
        public string resource_id { get; set; }
        public Field[] fields { get; set; }
        public Record[] records { get; set; }
        public int limit { get; set; }
        public int offset { get; set; }
        public int total { get; set; }
    }

    public class Field
    {
        public string type { get; set; }
        public string id { get; set; }
    }

    public class Record
    {
        public string MothCollectSpeciesID { get; set; }
        public string MothCollectDate { get; set; }
        public string County { get; set; }
        public string Township { get; set; }
        public string MothCollectPlace { get; set; }
        public string WGS84Lat { get; set; }
        public string WGS84Lon { get; set; }
        public string MothFamily { get; set; }
        public string MothScienceName { get; set; }
        public string MothPhotoURL { get; set; }
    }
}

星期一, 6月 08, 2020

[WebApi] Cors

閱讀官方文章 -在 ASP.NET Web API 2 中啟用跨原始來源要求 後,把能理解部分筆記下來

文章內容說明
瀏覽器安全性可防止網頁對另一個網域提出 AJAX 要求。 這種限制稱為「同源策略」,可防止惡意網站從另一個網站讀取敏感性資料。 不過,有時候您可能會想要讓其他網站呼叫您的 Web API。

跨原始來源資源分享(CORS)是 W3C 標準,可讓伺服器放寬相同的來源原則。 使用 CORS,伺服器可以明確允許某些跨源要求,然而拒絕其他要求。 CORS 比先前的技術(例如JSONP)更安全且更具彈性。

範例說明

如果兩個 Url 具有相同的配置、主機和埠,則具有相同的來源,以網站 http://example.com 為範例說明

URL結果原因
http://example.com/foo.html成功
http://example.com/bar.html成功
http://example.net失敗不同的網域
http://example.com:9000/foo.html失敗不同的埠
https://example.com/foo.html失敗不同的配置
http://www.example.com/foo.html失敗不同的子域

星期日, 6月 07, 2020

[X.Form] Messaging Center

打開 Xamarin Shell 官方範本時,發現 Messaging Center 功能,就根據官方文章 - Xamarin.Forms MessagingCenter 來了解並筆記

該範例重點
  • 用 MessagingCenter 傳遞參數至 ViewModel
  • 在 MainPage 內訂閱 Hi,並觸發 DisplayAlert,但是假如取消註冊的話,就不會出現 DisplayAlert

ViewModel
using System.Collections.ObjectModel;
using Xamarin.Forms;

namespace MessagingCenterDemo.ViewModel
{
    public class MainPageViewModel
    {
        // ListView 資料來源
        public ObservableCollection<string> Greetings { get; set; }

        public MainPageViewModel()
        {
            Greetings = new ObservableCollection<string>();

            MessagingCenter.Subscribe<MainPage>(this, "Hi", (sender) =>
            {
                Greetings.Add("哈囉");
            });

            MessagingCenter.Subscribe<MainPage, string>(this, "Hi", (sender, arg) =>
            {
                Greetings.Add("哈囉 " + arg);
            });
        }
    }
}
Xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             x:Class="MessagingCenterDemo.MainPage">

    <StackLayout Margin="20,35,20,20">
        <Label Text="MessagingCenter 範例"
               FontAttributes="Bold"
               FontSize="Large"
               HorizontalOptions="Center" />
        <Button x:Name="SayHiButton" 
                Text="哈囉"
                Clicked="SayHiButton_Clicked" />
        <StackLayout Orientation="Horizontal">
            <Entry x:Name="NameEntry" 
                   Placeholder="請輸入姓名" 
                   HorizontalOptions="FillAndExpand"/>
            <Button x:Name="SayHiToPersonButton" 
                    Text="跟某人說哈囉"
                    HorizontalOptions="EndAndExpand"
                    Clicked="SayHiToPersonButton_Clicked" />
        </StackLayout>
        <Button x:Name="OnUnsubscribeButton" 
                Text="取消訂閱"
                Clicked="OnUnsubscribeButton_Clicked" />

        <ListView ItemsSource="{Binding Greetings}" />
    </StackLayout>
</ContentPage>
Xaml 畫面顯示

[X.Form] Messaging Center-1

星期五, 6月 05, 2020

[SQL] 複合索引 - 排序

該筆記延續 [SQL] 複合索引 - 選擇性,不過因為方向不太一樣,所以就另外筆記一篇,主要是筆記下述 TSQL 語法適合的 Index
SELECT 
  CustomerID , 
  AVG(SubTotal)
FROM [Sales].[SalesOrderHeader]
WHERE OrderDate BETWEEN '日期區間'
GROUP BY CustomerID
結論
MS SQL 會比較 WHERE 條件和 GROUP BY 條件兩者成本,成本低者勝出就是
TSQL 中有 Order By 出現,會直覺有 Sort 操作子出現,但 GROUP BY 彙總資料前,資料假如沒有排序過,也會有 Sort 操作子出現,所以更明確定義方向為,WHERE 和 Sort 成本比較

以 AdventureWorks2017 [Sales].[SalesOrderHeader] 為資料,商業邏輯為查詢客戶在指定日期區間平均值

建立測試 Index

這兩個 Index 都 Include SubTotal 欄位,差異在於 OrderDate、CustimerID 誰是在第一欄位
DROP INDEX IF EXISTS IX_CustomerID_OrderDate ON [Sales].[SalesOrderHeader]
DROP INDEX IF EXISTS IX_OrderDate_CustomerID ON [Sales].[SalesOrderHeader]

CREATE NONCLUSTERED INDEX IX_CustomerID_OrderDate
ON [Sales].[SalesOrderHeader] (CustomerID , OrderDate)
INCLUDE ([SubTotal])

CREATE NONCLUSTERED INDEX IX_OrderDate_CustomerID
ON [Sales].[SalesOrderHeader] (OrderDate , CustomerID)
INCLUDE ([SubTotal])

星期四, 6月 04, 2020

PC 無法與機器通訊

被告知 CNC PC 網路異常,無法把檔案傳進 CNC 機器內,到現場就發現 PC 上通訊的西門子卡綠色燈號沒有亮,嘗試重開機也一樣,沒想到把電腦搬回檢修,一開機燈號就亮,整理過後覺得 PC 本身正常,把 PC 送回測試,沒想到在現場一開機燈號又不亮,靈光一閃把連接卡上的連接線拔除,重開機後燈號就亮,確認是因為 CNC 設備異常影響,所以燈號才不會亮,卡本身是正常的

一開機會自動進入 CNC 程式內,該流程不會出現任何錯誤訊息,但假如單獨開啟程式,會跳出下圖錯誤訊息

PC 無法與機器通訊-1

CNC 程式內,正常和異常的選單,廠長就是因為沒有 NC manager 和 Control Link 這兩個選項來判斷異常發生

PC 無法與機器通訊-2

星期二, 6月 02, 2020

[SQL] 複合索引 - 選擇性

論壇問題
兩個欄位複合索引,在應用的商業邏輯上都適合當第一欄位,那要選哪一個好
從統計資訊密度和選擇性來了解,結論為 [低密度高選擇],白話點就是看哪一個 WHERE 條件能找出最少資料,該條件就適合當第一欄位

以 AdventureWorks2017 [Sales].[SalesOrderHeader] 為資料,以查詢單一客戶本月訂單記錄為範例紀錄
------ 建立 IX_CustomerID_OrderDate 和 IX_OrderDate_CustomerID 兩個複合索引

DROP INDEX IF EXISTS IX_CustomerID_OrderDate ON [Sales].[SalesOrderHeader]
DROP INDEX IF EXISTS IX_OrderDate_CustomerID ON [Sales].[SalesOrderHeader]

CREATE NONCLUSTERED INDEX IX_CustomerID_OrderDate
ON [Sales].[SalesOrderHeader] (CustomerID , OrderDate)
INCLUDE ([SubTotal])

CREATE NONCLUSTERED INDEX IX_OrderDate_CustomerID
ON [Sales].[SalesOrderHeader] (OrderDate , CustomerID)
INCLUDE ([SubTotal])

------ 查詢客戶編號 11176 在 201307 的訂單紀錄
SELECT 
  CustomerID , 
  OrderDate , 
  SubTotal
FROM [Sales].[SalesOrderHeader]
WHERE OrderDate BETWEEN '20130701' AND '20130730'
  AND CustomerID = '11176'

執行計畫

[SQL] 複合索引欄位順序影響 - 選擇性-1

從執行計畫可以看出是使用 IX_CustomerID_OrderDate 來搜尋資料,原因在於 CustomerID = '11176' 篩選後資料筆數比較少

[SQL] 複合索引欄位順序影響 - 選擇性-2

從上圖就可以發現
  • OrderDate BETWEEN '20130701' AND '20130730' 條件,有 1528 筆資料
  • CustomerID = '11176' 條件,有 28 筆資料
所以 CustomerID 在該情境下,較適合當第一欄位

最後竟然兩者都適合當第一個欄位,那就指定 Index 來跑看看
----- 指定跑 IX_CustomerID_OrderDate
SELECT 
  CustomerID , 
  OrderDate , 
  SubTotal
FROM [Sales].[SalesOrderHeader] WITH (INDEX(IX_CustomerID_OrderDate))
WHERE OrderDate BETWEEN '20130701' AND '20130730'
  AND CustomerID = '11176'

----- 指定跑 IX_OrderDate_CustomerID
SELECT 
  CustomerID , 
  OrderDate , 
  SubTotal
FROM [Sales].[SalesOrderHeader] WITH (INDEX(IX_OrderDate_CustomerID))
WHERE OrderDate BETWEEN '20130701' AND '20130730'
  AND CustomerID = '11176'
[SQL] 複合索引欄位順序影響 - 選擇性-3

從執行計畫可以看出都是 Index Seek,但在執行計畫成本就分出差異

星期一, 6月 01, 2020

[C#] 更新主執行緒控件

Thread 執行完後,不可以直接去更改 Main Thread 上的控件,這概念聽過好幾次,筆記可以理解的使用方式,沒有包袱的話,以 async、await 為主,簡單好用

Xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             x:Class="AsyncAwait.UIControlUpdate">
    <ContentPage.Content>
        <StackLayout>
            <Button x:Name="ThreadExceptionButton" Text="執行 Thread 並拋出 Thread Excption"/>
            <Button x:Name="ThreadActionButton" Text="執行 Thread 並透過 Action 來更新控件"/>
            <Button x:Name="AsyncAwaitButton" Text="Async、Await 來更新控件"/>
            <Button x:Name="EssentialsButton" Text="Essentials 來更新控件"/>
            <Label x:Name="lblResult" Text="顯示結果" FontSize="Large" HorizontalOptions="Center" />
        </StackLayout>
    </ContentPage.Content>
</ContentPage>
C# Code
using System;
using System.Threading;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
using Xamarin.Essentials;

namespace AsyncAwait
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class UIControlUpdate : ContentPage
    {
        public UIControlUpdate()
        {
            InitializeComponent();
            ThreadExceptionButton.Clicked += (sender, e) => ThreadException();
            ThreadActionButton.Clicked += (sender, e) => ThreadAction();
            AsyncAwaitButton.Clicked += (sender, e) => AsyncAwait();
            EssentialsButton.Clicked += (sender, e) => Essentials();
        }

        private void ThreadException()
        {
            int waiting = 0;
            Thread t1 = new Thread(() =>
            {
                waiting = Waiting();

                // Xamarin 上的 Exception 錯誤訊息
                // Android.Util.AndroidRuntimeException
                // Message = Only the original thread that created a view hierarchy can touch its views.
                lblResult.Text = $"等待時間為 {waiting} ";
            });
            t1.Start();

            lblResult.Text = "等待中......";
        }

        private void ThreadAction()
        {
            int waiting = 0;
            Thread t1 = new Thread(() =>
            {
                waiting = Waiting();

                Action action = () => lblResult.Text = $"等待時間為 {waiting}";

                // Xamarin 寫法
                Device.BeginInvokeOnMainThread(action);
            });
            t1.Start();

            lblResult.Text = "等待中......";
        }

        private async void AsyncAwait()
        {
            Task<int> task = Task.Run(() => Waiting());
            lblResult.Text = "等待中......";
            int waiting = await task;
            lblResult.Text = $" 等待時間為 {waiting}";
        }

        private async void Essentials()
        {
            lblResult.Text = "等待中......";

            await Task.Run(() => 
            {
                int waiting = Waiting();

                // Xamarin.Essentials MainThread 寫法
                MainThread.BeginInvokeOnMainThread(() => 
                { 
                    lblResult.Text = $" 等待時間為 {waiting}";
                });
            });
        }

        /// <summary>
        /// 等待 5 秒
        /// </summary>
        /// <returns>5 秒</returns>
        private int Waiting()
        {
            int waitSecond = 5000;
            Thread.Sleep(waitSecond);
            return waitSecond;
        }
    }
}
UI 畫面:四個按鈕和一個標籤

[C#] 更新主執行緒控件-1 

操作 ThreadException() 會拋出 Exception 

[C#] 執行緒更新控件-2

操作 ThreadAction()、AsyncAwait() 和 Essentials() 畫面如下

[C#] 更新主執行緒控件-3

[C#] 更新主執行緒控件-4

BeginInvokeOnMainThread 

在 Xamarin 內有兩種方式可以呼叫 Main Thread 分別為 
  • Device.BeginInvokeOnMainThread(Action) 
  • MainThread.BeginInvokeOnMainThread(Action) 
 下圖為官方文章,說明兩者使用時機