iT邦幫忙

第 11 屆 iThome 鐵人賽

0
自我挑戰組

練習程式系列 第 37

C# 筆記

1 程式來源:async vs thread

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"Hello World! Main Thread:  {Thread.CurrentThread.ManagedThreadId }");
            AnotherMethod();
            Console.WriteLine($"Finish AnotherMethod():  {Thread.CurrentThread.ManagedThreadId }");
           // Console.Read();
        }

        static async void AnotherMethod()
        {
            await Task.Delay(2000);
            Console.WriteLine($"async await complete {Thread.CurrentThread.ManagedThreadId }");
        }
    }

結果:
https://ithelp.ithome.com.tw/upload/images/20200316/20111994RT8GxO2YTk.png

執行不到這行:

Console.WriteLine($"async await complete {Thread.CurrentThread.ManagedThreadId }");

按F11 逐行執行, 大概是因為 await Task.Delay(2000); 時 ,程式又回到
static void Main(string[] args) 裡面 , 所以印完Finish AnotherMethod() ,程式就結束了。

所以如果多加Console.Read();,因為程式還沒結束 , 所以就可以印出:

Console.WriteLine($"async await complete {Thread.CurrentThread.ManagedThreadId }");

結果:
https://ithelp.ithome.com.tw/upload/images/20200316/20111994AROBffTiBn.png

然後按F11 逐行執行,會發現用async、await ,系統還是會創建額外的執行緒(是因為await 這行 執行完後,為了要提醒 我完成了, 所以開了一個Worker Thread ? 但是這是非常輕量的thread ?)(async await的thread):
https://ithelp.ithome.com.tw/upload/images/20200316/20111994ZXCy941maj.png

自己創建一個Thread,會跟async、await系統創建的,不太一樣:
https://ithelp.ithome.com.tw/upload/images/20200316/20111994LUbBc3X1Y1.png

程式async await跑五次迴圈:

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"Hello World! Main Thread:  {Thread.CurrentThread.ManagedThreadId }");
            for (int i = 0; i < 5; i++)
           {
                Console.WriteLine($"i:  {i }");
                AnotherMethod();
            }
            Console.WriteLine($"Finish AnotherMethod():  {Thread.CurrentThread.ManagedThreadId }");
            Console.Read();
        }

        static async void AnotherMethod()
        {
            Console.WriteLine($"async await not complete {Thread.CurrentThread.ManagedThreadId }");
            await Task.Delay(20000);
            Console.WriteLine($"async await complete {Thread.CurrentThread.ManagedThreadId }");
        }
    }

這邊應該是真的有多條執行緒同步再跑嗎?,跟自己的電腦是幾核心有關係?,因為大概等20秒左右,五個async await complete就全部跳出來了,所以這4、7、8、5、6是多核平行跑,還是單核多個執行緒?(猜測async await這種系統的應該是單核多個執行緒,自己開的應該是多核平行跑,還是要看cpu決定?)
結果:

Hello World! Main Thread:  1
i:  0
async await not complete 1
i:  1
async await not complete 1
i:  2
async await not complete 1
i:  3
async await not complete 1
i:  4
async await not complete 1
Finish AnotherMethod():  1
async await complete 4
async await complete 7
async await complete 8
async await complete 5
async await complete 6

程式thread跑五次迴圈:

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"Hello World! Main Thread:  {Thread.CurrentThread.ManagedThreadId }");
             for (int i = 0; i < 5; i++)
             {
                Console.WriteLine($"i:  {i }");
                Thread x = new Thread(AnotherMethod1);
                 x.Start();
             }
            Console.WriteLine($"Finish AnotherMethod1():  {Thread.CurrentThread.ManagedThreadId }");
            Console.Read();
        }

        static void AnotherMethod1()
        {
            Console.WriteLine($"Thread not complete {Thread.CurrentThread.ManagedThreadId }");
            Task.Delay(2000);
            Console.WriteLine($"Thread complete {Thread.CurrentThread.ManagedThreadId }");
        }
    }

結果:

Hello World! Main Thread:  1
i:  0
i:  1
i:  2
i:  3
i:  4
Thread not complete 4
Thread not complete 6
Thread not complete 5
Finish AnotherMethod1():  1
Thread not complete 8
Thread not complete 9
Thread complete 4
Thread complete 6
Thread complete 5
Thread complete 8
Thread complete 9

參考:ASP.NET async 基本心法
看到:

.NET 實現非同步作業的做法是在需要等待時建立 SynchronizationContext,讓執行緒中斷作業先去處理其他作業,等待結束後再回頭繼續後半段。而繼續執行作業的執行緒可以是當初建立 SynchronizationContext 的那一條執行緒,也可以是其他執行緒。

換言之,若是一堆吃 CPU 的作業,增加執行緒肯定可以加速;若為要等待 I/O 回應的作業,增加執行緒用處不大,改為非同步作業才算對症下藥。

整理:
一 async await 是非同步作業,用來處理http的request、response,用的到

二 所以 > await Task.Delay(2000);
在await之後的程式 ,可能會新開執行緒,或是不開執行緒(根據 Task.Delay(2000) ,Task.Delay(2000)有開就會開新執行緒), 然後程式不會就在那邊等待 ,而是會跑回static void Main(string[] args),
完成後續的程式,在回到await之後的程式去跑

基本上對這塊一知半解,目前只會寫程式觀察執行順序,或是看看工作管理員。
找到教學,接著來練習:
新書發布: .NET 本事-非同步程式設計

程式來源:
async-book-support/Examples/Ch02/Ex10_Task/Program.cs

程式:

namespace ThreadTest03
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 1000; i++)
            {
                Thread t1 = new Thread(MyTask);
                t1.Start();
            }
        }

        private static void MyTask()
        {
            Thread.Sleep(100000);
        }

    }

}

這樣寫好像真的會開1000個執行緒 , 到工作管理員的詳細資料 -->右鍵 選取欄位 -- > 增加
執行緒 -- >找到 ThreadTest03.exe ,發現有1010個執行緒 ,記憶體是 46184 K
https://ithelp.ithome.com.tw/upload/images/20200522/20111994MGA0Q4ZfRH.png

然後可以在寫一個這樣的程式:

    class Program
    {
        static void Main(string[] args)
        {
            Console.ReadLine();
        }
    }

發現有10個執行緒,記憶體1856KB:
https://ithelp.ithome.com.tw/upload/images/20200522/201119941znGQ8ztii.png

所以 1010 - 10 =1000 ; 46184 -1856 = 44328
所以真的有開1000個執行緒? , 然後 一個執行緒大概4430K ?(這樣的方法好像不是很正確)
不過很多教學都寫一條執行緒1MB,所以用這方法推論應該是錯的

然後參考:
[c#]執行緒(thread)和執行緒集區(threadpool)的使用

t1.Abort(); 可以關執行緒
t1.IsAlive 檢查執行緒是否還活著

但是關掉一個執行緒,也需要一點時間,所以關掉後馬上檢查是否還活著,會發現執行緒還活著:

    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 1000; i++)
             {
                 Thread t1 = new Thread(MyTask);
                 t1.Start();
                 //Thread.Sleep(5);
                 t1.Abort();
                if (t1.IsAlive)//判斷是否還活著
                {
                    Console.WriteLine("t1 Alive");
                }
            }
            Console.ReadLine();
        }

        private static void MyTask()
        {
            Thread.Sleep(100000);
        }

    }

結果一堆t1 Alive ,但是把Thread.Sleep(5);的註解拿掉 , 應該就少很多t1 Alive了

2 WPF http get async 練習

程式來源:
C# Async / Await - Make your app more responsive and faster with asynchronous programming

平行運算 (一):Parallel.For、Parallel.Foreach 用法及技巧
How can I tell when HttpClient has timed out?
How to correctly write Parallel.For with async methods
HTTP Get and Post request in C#.net

練習三種按鈕,request 網址

1 async await 按鈕,但是是一個request結束後,才執行另一個request,所以跟3比,比較慢
畫面:
https://ithelp.ithome.com.tw/upload/images/20200316/20111994feSdbQhC9N.png

2 沒有async await 的按鈕 (時間先跑出來了)
畫面:
https://ithelp.ithome.com.tw/upload/images/20200316/201119942Ph45qPFGl.png

3 Task.WhenAll (看起來只是把很多個task聚在一起await,但是相比1速度快很多,因為1 是 一個request完成後,再 另一個 request,這個是 await 多個執行緒全部request完成。)
畫面:
https://ithelp.ithome.com.tw/upload/images/20200316/20111994GaaHuAwyh7.png

程式:
MainWindow.xaml.cs:

<Window x:Class="EasyGet.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:EasyGet"
        mc:Ignorable="d"
        Title="MainWindow"
        Height="450"
        Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>


        <Button Grid.Row="0"
                Click="get_Click01">Async Execute</Button>

        <Button Grid.Row="1"
                Click="get_Click02">Normal Execute</Button>

        <Button Grid.Row="2"
                Click="get_Click03">Parallel Async Execute</Button>
        
        <TextBlock x:Name="resultsWindow"
                   Grid.Row="3"
                   Margin="10" />
    </Grid>
</Window>

MainWindow.xaml:

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;


namespace EasyGet
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        //async
        private async void get_Click01(object sender, RoutedEventArgs e)
        {
            resultsWindow.Text = " ";
            var watch = System.Diagnostics.Stopwatch.StartNew();
            for (int i = 0; i < 10; i++)
            {
                await get_Http("https://ithelp.ithome.com.tw/");
            }
            watch.Stop();
            var elapsedMs = watch.ElapsedMilliseconds;
            resultsWindow.Text += $"Total execution time: { elapsedMs }" + Environment.NewLine;
        }

        //http async ,但是按鈕點擊沒有 async
        private void get_Click02(object sender, RoutedEventArgs e)
        {
            resultsWindow.Text = " ";
            var watch = System.Diagnostics.Stopwatch.StartNew();
            for (int i = 0; i < 10; i++)
            {
                get_Http("https://ithelp.ithome.com.tw/");
            }
            watch.Stop();
            var elapsedMs = watch.ElapsedMilliseconds;
            resultsWindow.Text += $"Total execution time: { elapsedMs }" + Environment.NewLine;
        }

        //async  全部http request
        private async void get_Click03(object sender, RoutedEventArgs e)
        {
            resultsWindow.Text = " ";
            var watch = System.Diagnostics.Stopwatch.StartNew();
            List<string> myLists = new List<string>(); //要request的資料
            for (int i = 0; i < 10; i++)
            {
                myLists.Add("https://ithelp.ithome.com.tw/");
            }

            List<Task> TaskLists = new List<Task>(); //task List
            foreach (string data in myLists)
            {
                TaskLists.Add(get_Http(data));
            }

            await Task.WhenAll(TaskLists);

            watch.Stop();
            var elapsedMs = watch.ElapsedMilliseconds;
            resultsWindow.Text += $"Total execution time: { elapsedMs }" + Environment.NewLine;
        }

        private async Task get_Http(String url)
        {
            using (HttpClient client = new HttpClient() { Timeout = TimeSpan.FromMilliseconds(3000) })
            {
                try
                {
                    using (HttpResponseMessage response = await client.GetAsync(url))
                    {
                        using (HttpContent content = response.Content)
                        {
                            string mycontent = await content.ReadAsStringAsync();
                            resultsWindow.Text += $"{ mycontent.Length }" + Environment.NewLine;
                        }
                    }
                }
                catch (WebException ex) //還不知道這是什麼
                {
                    resultsWindow.Text += $"WebException: { ex }" + Environment.NewLine;
                }
                catch (TaskCanceledException ex) //timeout事件
                {
                    resultsWindow.Text += $"TaskCanceledException: { ex }" + Environment.NewLine;
                }
            }
        }


    }
}

備註:
參考:What is TPL ( Task Parallel Library) and how it differs from threads (c# interview questions) ?
windows + R -- > perfmon (開啟效能監視器) -- > 右鍵新增計數器 -- >選擇CPU核心
-- >可以觀看每個CPU核心的執行狀況?
https://ithelp.ithome.com.tw/upload/images/20200316/20111994P6IPLDLPZF.png

3 Delegates

程式來源:C# Delegates explained
Delegates練習:

using System;

namespace DelegatesTest
{
    class Program
    {

        public delegate void TestMethodPtr();

        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            TestMethodPtr obj = new TestMethodPtr(TestMethod);
            obj.Invoke();

            TestClass obj1 = new TestClass();
            obj1.LongRunning(TestMethod1);
        }

        static void TestMethod()
        {
            Console.WriteLine("Hello TestMethod()");
        }

        static void TestMethod1(int i)
        {
            Console.WriteLine($"TestMethod1 {i}");
        }
    }

     class TestClass
    {
        internal delegate void CallBack(int i);
        internal void LongRunning(CallBack obj)
        {
            for (int i = 0; i < 1000; i++)
            {
                obj(i);
            }
        }
    }
}

4 Itextsharp

iTextSharp是舊的,現在是itext7,不過不會用itext7,就用iTextSharp

參考資料:
iTextSharp
itextsharp/src/core/iTextSharp/text/pdf/parser/LocationTextExtractionStrategy.cs
[C#][iTextSharp] PDF 套版
Getting Coordinates of string using ITextExtractionStrategy and LocationTextExtractionStrategy in Itextsharp
[.NET,iTextSharp]用程式呼叫cmd(command line)執行exe(以開啟adobe reader列印pdf為例)
Search Particular Word in PDF using Itextsharp
ItextSharp can't find PDF
[.NET]iTextsharp找不到字型檔問題(.ttf not found as file or resource)

itext7:
itext7
itext7-dotnet

iTextSharp整理

1 座標以PDF左下為(0,0),x往右+,y往上+

2 LocationTextExtractionStrategy 可以取得特定文字的座標,這樣可以把PDF的字用白色,然後在上面自己在增加圖片或文字。

3
這段可以取得當前PDF頁數(像是第一頁)的所有文字,
currentPageText -->有所有文字的內容

ITextExtractionStrategy strategy = new SimpleTextExtractionStrategy();
string currentPageText = PdfTextExtractor.GetTextFromPage(pdfReader, page, strategy);

5 dll

Possible to decompile DLL written in C?
[工具]反組譯的好工具-JustDecompile
[.NET] ILSpy 程式碼反組譯工具
ILSpy 6.0 Preview 2
C#_DLL反編譯功能_EXE(Application)和DLL(Application Extension)的差別在哪???_DLL的意義_反編譯目的

6 Parallel.For

C# Parallel.For 的效果威力
What is TPL ( Task Parallel Library) and how it differs from threads (c# interview questions) ?
如何:撰寫簡單的 Parallel.For 迴圈

7 WinSCard

Developing Simple APDU Sender using WinSCard
C# SCardGetStatusChange() check smartcard status
python 讀取健保IC卡資訊
What APDU command gets card ID
[Java] Java讀取健保卡基本資料(晶片讀卡機)
[C#] C# 語取健保卡基本資料─晶片讀卡機
整理:

1 WinSCard在哪?

C:\Windows\System32\WinSCard.dll

2 目前整個流程是:

SCardEstablishContext -- > SCardListReaders (找到所有讀卡機) -- > 用一個 List 把讀卡機的名稱都存起來 -- > 用List 裡的所有讀卡機的名稱 ,製作一個 SmartCardReaderState[] 的東西 -- >
SCardGetStatusChange 把 SmartCardReaderState[] 當參數帶入 --> 之後 跑 SmartCardReaderState[] 迴圈,一個一個檢查 卡片狀態 -- > 如果卡片狀態是Card Present 的 , 就用 SCardConnect 連接卡片
-- > 連接卡片成功的話,就可以SCardTransmit 送訊息給智慧卡 -- >SCardTransmit的 倒數第二個參數 ref byte pbRecvBuffer 就可以獲得 智慧卡給的資訊 -- >接著關閉連接卡片 SCardDisconnect -- >結束SCardReleaseContext

每個過程如果 回傳 的整數 != 0 ,就代表有錯誤

3

智慧卡應用協議數據單元

有兩種類別的APDU:命令APDU和響應APDU。
命令APDU由讀卡器發送到智慧卡-它包含了一個必選的4字節的頭部(CLA,INS,P1,P2)和0到255位元組的數據。
響應APDU由智慧卡發送到讀卡器-它包含了必選的2位元組的狀態字和0到256位元組的數據。

The SCardTransmit function sends an APDU command to the smart card and expects to receive APDU response from the card.

所以SCardTransmit應該就是 命令APDU 送訊息給智慧卡 ,智慧卡會在帶訊息回參數

4

這段程式的讀卡機型號好像是 EZ100PU
EZ100PU

不過測試是 這個也可以it-500u:
it-500u

5

判斷讀卡機的卡片狀態主要就是參考這段:
C# SCardGetStatusChange() check smartcard status

不過很多看不懂,大概只知道

readerState.eventState 去跟
public static readonly UInt32 Present = 0x00000020; //十進位就是32 // 二進位100000
public static readonly UInt32 Atrmatch = 0x00000040; // 十進位64 // 二進位1000000
用&運算,如果其中一個>0 ,就算 "有卡片在 讀卡機"

如果卡片晶片放錯邊,好像也算 "有卡片在 讀卡機"
不過卡片放錯邊,會在SCardConnect有錯誤 ,錯誤數字: -2146434970 ,不知道是不是數字都一樣

6

獲得的讀卡機字串長這樣,表示有兩個讀卡機:
Generic Smart Card Reader Interface 0 Generic Usb Smart Card Reader 0
然後結尾都是以0當結尾, 所以用0 split

7

裡面常看到in、out、ref:
C#雜記 — 參數修飾詞 in、out、ref
C# - Using in, out, and Ref with Parameters

整理
1 out 一定要new ,不然會有:

在程式控制權脫離目前的方法之前,必須指派 out 參數 'student'。

2
in的參數 ,則是 不能有new:

無法指派至 變數 'in Program.Student',因為它不是唯讀變數

沒辦法:

student = new Student();

但是可以

student.Enrolled = true;

因為

僅有物件本身(student)為唯獨,物件內的屬性(student.Enrolled )不受影響,因為是不同的參考位址

3
ref 就是 沒有限制

8

Smartcard 讀寫器函數庫的使用說明

參考:

1

SCardListReaders 第一次呼叫時並不知道機器有幾台可用之讀卡機所以mszReaders先代入NULL,取得實際數量後再呼叫一次就可列出讀卡機名稱。

所以這部分這樣寫:

ErrorInt = SCardListReaders(ContextHandle, null, null, out bufferLength); //先取得長度
Debug.WriteLine($"bufferLength{bufferLength}"); //71
ReaderList = new byte[bufferLength];
ErrorInt = SCardListReaders(ContextHandle, null, ReaderList, out bufferLength);

9

常看到錯誤6
可能原因:
System Error Codes (0-499)

ERROR_INVALID_HANDLE
6 (0x6)
The handle is invalid.

Error 6 when connecting Gemalto smart card

可能錯誤原因:

Try to check your OS (32-bit or 64-bit) and your smart card driver (32-bit or 64-bit). Ensure that they are the same. Also, check that your codes are using the correct variable type.

一 smart card driver要跟作業系統一致32-bit or 64-bit

二 查看[DllImport("WinScard.dll")] 裡的變數有沒有什麼錯誤


還不了解:
Buyer Beware: Change Smart Card Admin Key PIN

Is it possible to update admin key by apdu command ?
Usually the admin key is protected with checksum, proprietary CLA and INS, or specific APDUs sequence. Please note that this depends on the vendor implementation which is RARELY shared.


WinScard SCardConnect returns SCARD_E_NOT_READY

SCardConnect 有個參數是 uint dwShareMode,設定的值有

// This application will NOT allow others to share the reader. (SCARD_SHARE_EXCLUSIVE)
Exclusive = 0x0001,
// This application will allow others to share the reader. (SCARD_SHARE_SHARED)
Shared = 0x0002,
// Direct control of the reader, even without a card. (SCARD_SHARE_DIRECT)
Direct = 0x0003

如果設定1 ,開兩個程式去讀 卡片 ,就會有錯誤 。 所以改成2

3的話好像也有錯誤-2146435061 。所以如果有兩個程式同時去跑讀卡,都設定2才不會有誤


SCardGetStatusChange 這邊如果是在netcoreapp3.1 ,也會有6 ,在.NETFramework,Version=v4.7.2不會

10

SCardEstablishContext 程式一開始執行一次
SCardReleaseContext 程式關掉前執行一次

Circular Progressbar

想在WPF用個Circular Progressbar

一開始參考:
Circular Progressbar using C#

是在Windows Forms 畫的 , 會看起來閃閃的 , 所以不採用。
如何在WPF 使用Windows Forms 的東西?

一 增加System.Windows.Forms 、 WindowsFormsIntegration (加入參考,原本就有的東西,不用下載)

二 在 xaml增加

        <WindowsFormsHost x:Name="windowsFormsHost"
                          VerticalAlignment="Center"
                          Height="500"
                          Width="500"
                       >
            <WindowsFormsHost.Child>
                <wf:Panel x:Name="myPanel"
                          Paint="CircularProgressBar_Paint" />
            </WindowsFormsHost.Child>
            
        </WindowsFormsHost>

三 之後再CircularProgressBar_Paint 就可以畫圈圈了

後來找有沒有現成的套件:
C# Tutorial - Circle Progress Bar | FoxLearn
https://ithelp.ithome.com.tw/upload/images/20200614/20111994AVYNPjopix.png

不過我不會在WPF使用,所以就放棄了

最後參考這個,完成的
How To Create A ProgressCircle In C# With WPF

步驟:

1
好像是要先下載這個東西(youtube有附連結):
Microsoft Expression Blend 4 Software Development Kit (SDK) for .NET 4
https://www.microsoft.com/zh-tw/download/confirmation.aspx?id=10801

然後下載的dll會在

C:\Program Files (x86)\Microsoft SDKs\Expression\Blend\.NETFramework\v4.0\Libraries\Microsoft.Expression.Drawing.dll

2
之後這邊作者有提供code,在youtube有附連結 , 只要加入Microsoft.Expression.Drawing.dll的參考,就可以跑程式了。

3
Cannot find invalidate method for MainWindow

invalidate -- Windows Forms
InvalidateVisual() -- WPF
先用InvalidateVisual()吧!還不知道RedrawWindow function是什麼

4
修改ProgressCircle 的 value 值 1 -100 ,就有轉圈圈的效果

測試

[.NET]iTextsharp找不到字型檔問題(.ttf not found as file or resource)
【python內建模組- unitest】單元測試
Creating your first unit test


上一篇
Android,Navigation
系列文
練習程式37
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
小碼農米爾
iT邦高手 1 級 ‧ 2020-03-16 13:56:59

await 之後不一定會在新的執行緒

要看後面接的方法裡面有沒有另外開

比較好的說法是 Task.Delay(2000) 這個方法裡面會開新的執行緒

喔喔,了解,謝謝

我要留言

立即登入留言