C# LINQ JOIN 函式效果

前言

因為平時都是寫lambda寫法的LINQ,而在近期看到函式方式的寫法,所以紀錄一下作法。

JOIN函式定義

1
2
3
4
5
6
7
8
public static System.Collections.Generic.IEnumerable<TResult> 
Join<TOuter,TInner,TKey,TResult> (
this System.Collections.Generic.IEnumerable<TOuter> outer,
System.Collections.Generic.IEnumerable<TInner> inner,
Func<TOuter,TKey> outerKeySelector,
Func<TInner,TKey> innerKeySelector,
Func<TOuter,TInner,TResult> resultSelector,
System.Collections.Generic.IEqualityComparer<TKey>? comparer);

參數部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
outer
IEnumerable<TOuter>
要聯結的第一個序列。

inner
IEnumerable<TInner>
要加入第一個序列的序列。

outerKeySelector
Func<TOuter,TKey>
用來從第一個序列各個項目擷取聯結索引鍵的函式。

innerKeySelector
Func<TInner,TKey>
用來從第二個序列各個項目擷取聯結索引鍵的函式。

resultSelector
Func<TOuter,TInner,TResult>
用來從兩個相符項目建立結果項目的函式。

comparer
IEqualityComparer<TKey>
用來雜湊及比較索引鍵的 IEqualityComparer<T>。

實作

簡單來講就是拿主表資料去JOIN另一張關聯表,而x、y則是塞進去的key值。
在這裡以User為主表,UserDetail為關聯表。
原本4筆資料的User,經過與UserDetail JOIN關聯,於是剩下兩筆資料。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace JoinSample
{

public class User
{
public int Id { get; set; }
public string Name { get; set; }
}

public class UserDetail
{
public int UserId { get; set; }
public string PhoneNumber { get; set; }
}


public class UserData
{
public int Id { get; set; }

public string UserName { get; set; }
public string PhoneNumber { get; set; }
}

internal class Program
{
static void Main(string[] args)
{
var users = new List<User>
{
new User
{
Id =1,Name="大雄",
},
new User
{
Id =2,Name="多拉A夢",
},
new User
{
Id =3,Name="胖虎",
},
new User
{
Id =4,Name="小夫",
}
};

var userDetails = new List<UserDetail> {
new UserDetail{ UserId=1,PhoneNumber="0911111111"},
new UserDetail{ UserId=2,PhoneNumber="0922222222"}
};

var userData = users.Join(
userDetails,
x => x.Id,
y => y.UserId,
(x, y) => new UserData
{
Id = x.Id,
PhoneNumber = y.PhoneNumber,
UserName = x.Name
});

foreach (var item in userData)
{
Console.WriteLine(item.Id);
Console.WriteLine(item.UserName);
Console.WriteLine(item.PhoneNumber);
}
}
}
}

output:

1
2
3
4
5
6
1
大雄
0911111111
2
多拉A夢
0922222222

參考資料

[Day7]C#建立不會停止的背景Task

前言

在上回 Post not found: Task運行處理-Run、Start、RunSynchronously-C-SyncAndAsync [Day6]Task運行處理(Run、Start、RunSynchronously) ,我們學習了Task的各種啟動方式。
而今回主要來撰寫不會停止的背景Task。

實作

不終止的非同步事件 ex:多個while(true)常駐。

主要使用以下的模板去製作常駐程式:

1
2
3
4
5
6
7
var endlessTask = Task.Run(async () =>
{
while (true)
{
// Todo
}
});

簡單來說就是開一個異步的Task讓他跑即可。

在以下的範例中endlessTask1、endlessTask2會不停的印出資料。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
using System;
using System.Threading.Tasks;

namespace EndlessTaskAsync
{
class Program
{
/// <summary>
/// 主程式
/// </summary>
/// <returns></returns>
public static async Task Main()
{
var endlessTask1 = EndlessTask(1);
var endlessTask2 = EndlessTask(2);

await PreventToStop();
}

/// <summary>
/// 製作無止盡的 while task
/// </summary>
/// <param name="number">編號</param>
/// <returns></returns>
private static Task EndlessTask(int number)
{
Console.WriteLine("Start EndlessTask" + number);
var endlessTask = Task.Run(async () =>
{
while (true)
{
Console.WriteLine("EndlessTask" + number);
await Task.Delay(100);
}
});
return Task.CompletedTask;
}

/// <summary>
/// 避免視窗關閉,使用ReadKey();
/// </summary>
/// <returns></returns>
private static Task PreventToStop()
{
Console.ReadKey();
return Task.CompletedTask;
}
}
}

運行結果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
EndlessTask1
EndlessTask2
EndlessTask1
EndlessTask2
EndlessTask1
EndlessTask2
EndlessTask1
EndlessTask2
EndlessTask1
EndlessTask2
EndlessTask1
EndlessTask2
EndlessTask1
EndlessTask2
EndlessTask1
EndlessTask2
EndlessTask1
EndlessTask2

直到關掉程式都會一直印。

總結

在這回中練習了背景常駐的Task,通常會在監聽某個請求while中使用。
如果是以目前處理過的code,就是在socket處理上會大量使用。

專案存庫:
https://github.com/yuhsiang237/EndlessTaskAsync

C# Tuple實名、匿名用法

最近在寫資料時需要回傳多筆數,但在資料單純、沒特別的邏輯的情況下不想再建立一個Model回傳,這時就可以使用Tuple返回元組類型。

以下為匿名、實名的兩種Tuple寫法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System;

namespace TupleSample
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine(String.Concat(GetUser1().Id, GetUser1().Name));
Console.WriteLine(String.Concat(GetUser2().Item1, GetUser2().Item2));
}

static (int Id, string Name) GetUser1()
{
return (5, "王小明");
}


static Tuple<int, string> GetUser2()
{
return Tuple.Create(5, "王小明");
}
}
}

output

1
2
5王小明
5王小明

總結

當返回值較為單純時可以使用,如2~3個返回值。

參考資料

https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/builtin-types/value-tuples

[Day6]Task運行處理(Run、Start、RunSynchronously)

前言

在上回 Post not found: Task等待處理-Wait、WaitAll、WaitAny、WaitAsync、WhenAll、WhenAny-C-SyncAndAsync [Day5]Task等待處理(Wait、WaitAll、WaitAny、WaitAsync、WhenAll、WhenAny) - C# SyncAndAsync ,我們使用了Task來測試等待的效果,而這回主要探討的是執行運行。

  • Task.Run:將指定在 ThreadPool 執行工作排入佇列,並傳回該工作的工作或 Task 控制代碼。
  • Task.Start:啟動 Task,並排定它在目前的 TaskScheduler 執行。
  • Task.RunSynchronously:在目前的 Task 上同步執行 TaskScheduler。

讓一個Task運行的兩種做法

其實Task.Run、Task.Start都是能達到一樣的結果,只是寫法一個是分兩段,一個是一氣呵成。

1.第一種用new Task搭配Task.Start,必須寫兩段。好處是可以先建立了,再決定哪時啟用。

1
2
3
4
5
public static void Main(string[] args)
{
Task task = new Task(() => Console.WriteLine("task"));
task.Start();
}

2.第二種用Run,一氣呵成。執行當下就直接運行。

1
2
3
4
5
6
7
public static void Main(string[] args)
{
Task Task = Task.Run(() =>
{
Console.WriteLine("task");
});
}

因此以上兩種用法Task.Run、Task.Start的差別已經清楚了。
再來則是RunSynchronously用法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
public static void Main()
{
Console.WriteLine("Application executing on thread {0}",
Thread.CurrentThread.ManagedThreadId);
var asyncTask = Task.Run(() => {
Console.WriteLine("Task {0} (asyncTask) executing on Thread {1}",
Task.CurrentId,
Thread.CurrentThread.ManagedThreadId);
long sum = 0;
for (int ctr = 1; ctr <= 1000000; ctr++)
sum += ctr;
return sum;
});
var syncTask = new Task<long>(() => {
Console.WriteLine("Task {0} (syncTask) executing on Thread {1}",
Task.CurrentId,
Thread.CurrentThread.ManagedThreadId);
long sum = 0;
for (int ctr = 1; ctr <= 1000000; ctr++)
sum += ctr;
return sum;
});
syncTask.RunSynchronously();
Console.WriteLine();
Console.WriteLine("Task {0} returned {1:N0}", syncTask.Id, syncTask.Result);
Console.WriteLine("Task {0} returned {1:N0}", asyncTask.Id, asyncTask.Result);
}
}

執行結果:

1
2
3
4
5
6
Application executing on thread 1
Task 2 (syncTask) executing on Thread 1
Task 1 (asyncTask) executing on Thread 4

Task 2 returned 500,000,500,000
Task 1 returned 500,000,500,000

官方給的解釋:
在這兩種情況下,工作都會執行相同的 lambda 運算式,以顯示工作識別碼和工作執行所在之執行緒的識別碼。此工作會計算介於1到1000000之間的整數總和。 如範例的輸出所示,藉由呼叫方法來執行的工作 RunSynchronously 會在應用程式執行緒上執行,而非同步工作則不是。

簡單來說就是:RunSynchronously會在主執行序上面跑。而非同步的則會在其他執行序。

總結

在這回中可以知道讓Task執行有哪幾種作法。

參考資料

[Day5]Task等待處理(Wait、WaitAll、WaitAny、WaitAsync、WhenAll、WhenAny) - C# SyncAndAsync

前言

在上回 Post not found: 使用Task撰寫第一個非同步程式-C-SyncAndAsync [Day4]使用Task撰寫第一個非同步程式 - C# SyncAndAsync ,我們使用了Task來撰寫第一隻非同步的程式。
而在這回將會著重在等待方面。

Task提供以下語法能夠等待:Wait、WaitAll、WaitAny、WaitAsync、WhenAll、WhenAny。

  • Task.Wait:等候 Task 完成執行。
  • Task.WaitAll:等待所有提供的 Task 物件完成執行。
  • Task.WaitAny:等候任一提供的 Task 物件完成執行。
  • Task.WaitAsync:取得 Task 當此 Task 完成或指定的超時時間過期時將完成的。(ps.是.NET6, 7 Preview 1才支援的新語法,目前是用.net core 3.1所以先不做範例)
  • Task.WhenAll:建立當所有提供的工作完成時才會完成的工作。
  • Task.WhenAny:建立當任何一個提供的工作完成時才會完成的工作。

這麼看來,使用Wait、When差別好像很難分清楚?再稍微比較一下:

Task.WhenAll Task.WaitAll
調用時阻塞該線程 不會
返回值 Task
備註 可以用返回的Task去檢查是否完成

此外,Task.WhenAll()也能用前面加await的語法去製造出阻塞。

1
await Task.WhenAll()

因此在使用上,能夠全部使用When再搭配await去組合出Wait的效果。

實作測試

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;

namespace TaskWaitHandle
{
public class Program
{
public static void Main(string[] args)
{
TaskWaitTest();

TaskWaitAllTest();
TaskWhenAllTest();

TaskWaitAnyTest();
TaskWhenAnyTest();
}

/// <summary>
/// 測試Task.WaitAny
/// </summary>
public static void TaskWaitAnyTest()
{
Stopwatch sw = new Stopwatch();
sw.Start();


var tasks = new List<Task> { DelayTime(5000), DelayTime(3000), DelayTime(2000) };
Task.WaitAny(tasks.ToArray());

string second = (sw.Elapsed.TotalMilliseconds / 1000).ToString();
Console.WriteLine(nameof(TaskWaitAnyTest) + " : 一共花費:" + second + "秒");
}

/// <summary>
/// 測試Task.WhenAny
/// </summary>
public static void TaskWhenAnyTest()
{
Stopwatch sw = new Stopwatch();
sw.Start();


var tasks = new List<Task> { DelayTime(5000), DelayTime(3000), DelayTime(2000) };
var t = Task.WhenAny(tasks);

string second = (sw.Elapsed.TotalMilliseconds / 1000).ToString();
Console.WriteLine(nameof(TaskWhenAnyTest) + " : 一共花費:" + second + "秒");
}

/// <summary>
/// 測試Task.WhenAll
/// </summary>
public static void TaskWhenAllTest()
{
Stopwatch sw = new Stopwatch();
sw.Start();


var tasks = new List<Task> { DelayTime(5000), DelayTime(3000), DelayTime(2000) };
var t = Task.WhenAll(tasks);

string second = (sw.Elapsed.TotalMilliseconds / 1000).ToString();
Console.WriteLine(nameof(TaskWhenAllTest) + " : 一共花費:" + second + "秒");
}

/// <summary>
/// 測試Task.WaitAll
/// </summary>
public static void TaskWaitAllTest()
{
Stopwatch sw = new Stopwatch();
sw.Start();


var tasks = new List<Task> { DelayTime(5000), DelayTime(3000), DelayTime(2000) };
Task.WaitAll(tasks.ToArray());

sw.Stop();
string second = (sw.Elapsed.TotalMilliseconds / 1000).ToString();
Console.WriteLine(nameof(TaskWaitAllTest) + " : 一共花費:" + second + "秒");
}

/// <summary>
/// 測試Task.Wait
/// </summary>
public static void TaskWaitTest()
{
Stopwatch sw = new Stopwatch();
sw.Start();


Task t = DelayTime(5000);
t.Wait();
sw.Stop();


string second = (sw.Elapsed.TotalMilliseconds / 1000).ToString();
Console.WriteLine(nameof(TaskWaitTest) + " : 一共花費:" + second + "秒");
}

/// <summary>
/// 停止一段時間
/// </summary>
/// <param name="millseconds">停止毫秒</param>
/// <returns></returns>
public static async Task DelayTime(int millseconds)
{
await Task.Delay(millseconds);
}
}
}

執行完結果:

1
2
3
4
5
TaskWaitTest : 一共花費:5.0991762秒
TaskWaitAllTest : 一共花費:5.017742599999999秒
TaskWhenAllTest : 一共花費:0.0008574秒
TaskWaitAnyTest : 一共花費:2.0228913秒
TaskWhenAnyTest : 一共花費:0.0006043秒

可以見到When不會阻塞所以直接被帶過,而Wait造成阻塞。

總結

可以知道Task使用Wait會阻塞,When不會阻塞。
而使用When可以再之後去檢查該返回值是否完成。

至於如果想讓When阻塞,就使用await即可。
所以總結是:When搭await可以組出所有阻塞/非阻塞,因此一路組合技用到底即可,而Wait可以擺一邊放置了。

參考資料

[Day4]使用Task撰寫第一個非同步程式 - C# SyncAndAsync

前言

在上回 Post not found: C-中Task與Thread比較-C-SyncAndAsync [Day3]C#中Task與Thread比較 - C# SyncAndAsync ,比較了Task、Thread,而這回主要會以Task撰寫第一個非同步程式。

什麼是Task

Task類別代表不會傳回值,而且通常會以非同步方式執行的單一作業。 Task物件是工作架構非同步模式的其中一個核心元件,第一次是在 .NET Framework 4 中引進。 由於物件所執行的工作 Task 通常會線上程集區執行緒上以非同步方式執行,而不是在主應用程式執行緒上同步執行,因此您可以使用 Status 屬性以及 IsCanceled 、 IsCompleted 和 IsFaulted 屬性來判斷工作的狀態。 最常見的情況是使用 lambda 運算式來指定工作要執行的工作。

針對傳回值的作業,您可以使用 Task<TResult> 類別。

需求

我們有五件事情分別如下:

  1. 起床
  2. 打遊戲
  3. 聽音樂
  4. 吃零食
  5. 睡覺

而我們預計起床->同時執行打遊戲、聽音樂、吃零食->睡覺。
整體的順序會如下圖:

程式撰寫

再來我們快速撰寫一下程式碼。

我們將以下的事情都設定一個執行的時間:

  1. 起床,2秒
  2. 打遊戲,5秒
  3. 聽音樂,4秒
  4. 吃零食,4秒
  5. 睡覺,3秒

為了要使程式能夠等待,因此我們在函式上需要加入async,才能再裡面寫await。(不然只能用後面加上.Wait()的作法,有點鱉就是)

我在前後都加上了Stopwatch碼表,用來計時,如果照理論,那完成時間是10秒。
因為2~4項是併發異步執行只要取最高的打遊戲。
即:2+5+3=10

在每個事情裡面使用
await Task.Delay(5000);
去設定每件事情做的時間

21~24行則呼叫事情,並且把它加入List。
利用await Task.WhenAll去等待所有List中Task執行完畢後才執行接下來的事情。

程式碼放置庫:https://github.com/yuhsiang237/TaskAsync

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;

namespace TaskAsync
{
public class Program
{
/// <summary>
/// 主程式
/// </summary>
/// <returns></returns>
public static async Task Main()
{
Stopwatch sw = new Stopwatch();
sw.Start();

await WakeUpAsync();

IList<Task> tasks = new List<Task>();
tasks.Add(PlayGameAsync());
tasks.Add(ListenMusicAsync());
tasks.Add(EatSnackAsync());

await Task.WhenAll(tasks);

await GoToBedSleepAsync();

sw.Stop();
string second = (sw.Elapsed.TotalMilliseconds / 1000).ToString();
Console.WriteLine("一共花費:" + second + "秒");
}

/// <summary>
/// 模擬打遊戲
/// </summary>
/// <returns></returns>
private static async Task PlayGameAsync()
{
Console.WriteLine("打遊戲");
await Task.Delay(5000);
}

/// <summary>
/// 模擬聽音樂
/// </summary>
/// <returns></returns>
private static async Task ListenMusicAsync()
{
Console.WriteLine("聽音樂");
await Task.Delay(4000);
}

/// <summary>
/// 模擬吃零食
/// </summary>
/// <returns></returns>
private static async Task EatSnackAsync()
{
Console.WriteLine("吃零食");
await Task.Delay(4000);
}

/// <summary>
/// 模擬起床
/// </summary>
/// <returns></returns>
private static async Task WakeUpAsync()
{
Console.WriteLine("起床");
await Task.Delay(2000);
}

/// <summary>
/// 模擬睡覺
/// </summary>
/// <returns></returns>
private static async Task GoToBedSleepAsync()
{
Console.WriteLine("睡覺");
await Task.Delay(3000);
}
}
}

執行結果:

1
2
3
4
5
6
起床
打遊戲
聽音樂
吃零食
睡覺
一共花費:10.1614089秒

運行:

總結

使用Task去處理同步、異步上手挺容易的!
然比較需注意的點在如果你要在裡面用await,外層一定要加上async,否則會報錯。

程式碼放置庫:https://github.com/yuhsiang237/TaskAsync

參考資料

[Day3]C#中Task與Thread比較 - C# SyncAndAsync

前言

在上回 Post not found: 什麼是同步與異步-C-SyncAndAsync [Day2]什麼是同步與異步 - C# SyncAndAsync ,列出了同步異步差異,而今回就朝異步的Task、Thread邁進。

什麼是Task與Thread

在此將Task、Thread做一個表格比較:

Task Thread
描述 Task類別代表不會傳回值,而且通常會以非同步方式執行的單一作業。 Task物件是工作架構非同步模式的其中一個核心元件,第一次是在 .NET Framework 4 中引進。 由於物件所執行的工作 Task 通常會線上程集區執行緒上以非同步方式執行,而不是在主應用程式執行緒上同步執行,因此您可以使用 Status 屬性以及 IsCanceled 、 IsCompleted 和 IsFaulted 屬性來判斷工作的狀態。 最常見的情況是使用 lambda 運算式來指定工作要執行的工作。

針對傳回值的作業,您可以使用Task<TResult>類別。

可建立和控制執行緒,設定執行緒的優先權,並取得它的狀態。 Thread是C#中最早的多執行緒模型,後來才推出Task。
基於 基於Action,Func的更加現代的執行緒模型。支援模板引數。 基於delegate。僅受限於固定引數。
C#中常配套的關鍵詞 async、await Start、Suspend、Resume、Join、Abort、ThreadPool
namespace System.Threading.Tasks System.Threading
備註 Task的目的,就是要替代Thread,未來可能比較主流 -

總結

可以得知Task、Thread都是能夠處理異步的解決方案,而Task算是Thread的改良易用的封裝版本,因此接下來會先以Task為主進行深究,最後才是Thread。

而在MSDN官方也提供了一個非同步程式的配套設計模式:
https://docs.microsoft.com/zh-tw/dotnet/standard/asynchronous-programming-patterns/

參考資料

[Day2]什麼是同步與異步 - C# SyncAndAsync

前言

在上回 [Day1]為了某個夢想而開始 - C# SyncAndAsync ,列出了學習方向,而今回就朝學習目標邁進。

什麼是同步與異步

在此將同步、非同步做一個表格比較:

異步 同步
描述 又稱非同步(Asynchronous、async),調用在發出之後,這個調用就直接返回了,所以沒有返回結果。當一個異步過程調用發出後,調用者不會立刻得到結果。而是在調用發出後,被調用者通過狀態、通知或通過回調函數,讓調用者能響應結果。 又稱同步(Synchronous、sync),同步還可以理解為:發出一個調用時,在沒有得到結果之前,該調用就不返回。
使用場景 同時,一次做多件事情 同時,一次做一件事情
圖例
例子 非同步串行通信、AJAX -
C#中常見的關鍵詞 async、await、Task、Thread -

總結

在這篇中可以得知,如果想加快某些代碼執行的速度,而其彼此又不相干,就能將其以非同步方法實作。
且非同步方法調用後立即返回,因此不會有結果,而是用通知、回調方式去獲得結果。
而在C#中Thread與Task都能做到異步,下回則主要介紹Thread與Task的差異囉。

參考資料

[Day1]為了某個夢想而開始 - C# SyncAndAsync

前言

為什麼建立這個Topic呢?
因為想要完成一個小時候自己的夢想,也就是建立一個多人連線機制的小遊戲或系統(目前目標是PS4 4人連線)。
裡面有牽扯到一些同步、非同步操作。
因此,就必須學會在C#中操作非同步。
預計以7周為主專注在同步異步中,並且把可能常見的點給舉例出來。(7周能搞定我也不清楚,就至少會分7篇完成它。)

規劃

會以C#語言去撰寫
預計在之後會提到的:
Thread、Task、Callback、Await、Async。

將會比較彼此差別、實際撰寫一些小範例,並且簡單畫一下UML。

總結

最終,目標就是學透,然後把這應用在Socket編程上。
(一切都是興趣使然)