【讀書筆記】C#高級編程 第二十一章 任務、線程和同步

来源:http://www.cnblogs.com/dlxh/archive/2017/04/30/6788964.html
-Advertisement-
Play Games

(一)概述 所有需要等待的操作,例如,因為文件、資料庫或網路訪問都需要一定的時間,此時就可以啟動一個新的線程,同時完成其他任務。 線程是程式中獨立的指令流。 (二)Paraller類 Paraller類是對線程的一個很好的抽象,該類位於System.Threading.Tasks名稱空間中,提供了數 ...


(一)概述

所有需要等待的操作,例如,因為文件、資料庫或網路訪問都需要一定的時間,此時就可以啟動一個新的線程,同時完成其他任務。

線程是程式中獨立的指令流。

 

(二)Paraller類

Paraller類是對線程的一個很好的抽象,該類位於System.Threading.Tasks名稱空間中,提供了數據和任務並行性。

Paraller.For()和Paraller.ForEach()方法在每次迭代中調用相同的代碼,二Parallel.Invoke()方法允許同時調用不同的方法。Paraller.Invoke用於任務並行性,而Parallel.ForEach用於數據並行性。

 

1、Parallel.For()方法迴圈

ParallelLoopResult result = Parallel.For(0, 10, i =>
{
    Console.WriteLine("當前迭代順序:" + i);
    Thread.Sleep(10);//線程等待
});

For()方法中,前兩個參數定義了迴圈的開頭和結束,第三個參數是一個Action<int>委托,參數是迴圈迭代的次數。

Parallel類只等待它創建的任務,而不等待其他後臺活動。

Parallel.For()方法可以提前停止:

var result = Parallel.For(10, 40, async (int i, ParallelLoopState pls) =>
 {
     Console.WriteLine("迭代序號:{0}, 任務: {1}, 線程: {2}", i, Task.CurrentId, Thread.CurrentThread.ManagedThreadId);
     await Task.Delay(10);
     if (i > 15)
     {
         pls.Break();
     }
 });
Console.WriteLine("迴圈完成狀態:" + result.IsCompleted);
Console.WriteLine("Break索引:" + result.LowestBreakIteration);

需要註意的是,Break()方法僅是告知迴圈在合適的時候退出當前迭代之外的迭代。

Parallel.For()還可以對線程進行初始化和退出時制定方法:

Parallel.For<string>(10,25,()=> {
    Console.WriteLine("初始線程{0},任務{1}",Thread.CurrentThread.ManagedThreadId,Task.CurrentId);
    return string.Format("線程Id"+ Thread.CurrentThread.ManagedThreadId);
},
(i,pls,str1)=> {
    Console.WriteLine("迭代順序:【{0}】,線程初始化返回值:【{1}】,線程Id:【{2}】,任務Id:【{3}】",i,str1, Thread.CurrentThread.ManagedThreadId, Task.CurrentId);
    Thread.Sleep(10);
    return string.Format("迭代順序:"+i);
},
(str1)=> {
    Console.WriteLine("線程主體返回值:{0}",str1);
});

除了迴圈開頭與結束的指定,第三個是對迭代調用的每個線程進行處理,第四個是迭代的方法主體,第四個是迭代完成時對線程的處理。

 

2、使用Paralle.ForEach()方法迴圈

string[] data = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k" };
Parallel.ForEach(data, (s, pls, l) =>
{
    Console.WriteLine(s + " " + l);//s是當前迴圈的項的值,pls是ParallelLoopState類型,l是當前迭代的順序
});

 

3、通過Parallel.Invoke()方法調用多個方法

Parallel.Invoke()方法運行傳遞一個Action委托數組,在其中可以指定需要並行運行的方法。

 1 static void Main(string[] args)
 2 {
 3     Parallel.Invoke(Say1, Say2, Say3, Say4, Say5);
 4     Console.WriteLine("---------");
 5     Say1();
 6     Say2();
 7     Say3();
 8     Say4();
 9     Say5();
10  
11     Console.ReadKey();
12 }
13 static void Say1()
14 {
15     Thread.Sleep(100);
16     Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff") + "1");
17 }
18 static void Say2()
19 {
20     Thread.Sleep(100);
21     Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff") + "2");
22 }
23 static void Say3()
24 {
25     Thread.Sleep(100);
26     Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff") + "3");
27 }
28 static void Say4()
29 {
30     Thread.Sleep(100);
31     Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff") + "4");
32 }
33 static void Say5()
34 {
35     Thread.Sleep(100);
36     Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ffff") + "5");
37 }

 

 

(三)任務

為了更好的控制並行動作,可以使用System.Threading.Tasks名稱空間中的Task類。

 

1、啟動任務

(1)使用線程池的任務

 1 private static readonly object locker = new object();
 2 static void Main(string[] args)
 3 {
 4     var tf = new TaskFactory();
 5     Task t1 = tf.StartNew(TaskMethod, "使用TaskFactory");
 6  
 7     Task t2 = Task.Factory.StartNew(TaskMethod, "使用Task.Factory");
 8  
 9     var t3 = new Task(TaskMethod, "使用Task構造函數並啟動");
10     t3.Start();
11  
12     Task t4 = Task.Run(() => { TaskMethod("運行"); });
13  
14     Console.ReadKey();
15 }
16 static void TaskMethod(object title)
17 {
18     lock (locker)
19     {
20         Console.WriteLine(title);
21         Console.WriteLine("任務Id:{0},線程Id:{1}", Task.CurrentId == null ? "no Task" : Task.CurrentId.ToString(), Thread.CurrentThread.ManagedThreadId);
22         Console.WriteLine("是否為線程池線程:{0}", Thread.CurrentThread.IsThreadPoolThread);
23         Console.WriteLine("是否為後臺線程:{0}",Thread.CurrentThread.IsBackground);
24         Console.WriteLine();
25     }
26 }

 

(2)同步任務

任務不一定要使用線程池中的線程,也可以使用其他線程。

TaskMethod("主線程調用");
var t1 = new Task(TaskMethod,"同步運行");
t1.RunSynchronously();

 

(3)使用單獨線程的任務

如果任務的代碼應該長時間運行,就應該使用TaskCreationOptions.LongRunning告訴任務調度器創建一個新線程,而不是使用線程池中的線程。

var t1 = new Task(TaskMethod, "長時間運行任務", TaskCreationOptions.LongRunning);
t1.Start();

 

2、Future——任務的結果

任務結束時,它可以把一些有用的狀態信息寫到共用對象中,這個共用對象必須是線程安全的。另一個選項是使用返回某個結果的任務,如Future它是Task類的一個泛型版本,使用這個類時,可以定義任務返回的結果的類型。

var t1 = new Task<Tuple<int, int>>(TaskWithResult, Tuple.Create<int, int>(10, 5));
t1.Start();
Console.WriteLine(t1.Result);
t1.Wait();
Console.WriteLine("任務結果:{0} {1}",t1.Result.Item1, t1.Result.Item2);

 

3、連續的任務

通過任務,可以指定在任務完成後,應開始運行另一個特定任務。

Task t1 = new Task(DoOnFirst);
t1.Start();
Task t2 = t1.ContinueWith(DoOnSecond);
Task t3 = t1.ContinueWith(DoOnSecond);
Task t4 = t2.ContinueWith(DoOnSecond);
Task t5 = t3.ContinueWith(DoOnSecond, TaskContinuationOptions.OnlyOnFaulted);//第二個參數指t3在失敗的情況下運行t5

 

4、任務層次結構

任務也可以構成一個層次結構。一個任務啟動一個新任務時,就啟動了一個父/子層次結構。

 1 static void Main(string[] args)
 2 {
 3     var parent = new Task(ParentTask);
 4     parent.Start();
 5     Thread.Sleep(2000);
 6     Console.WriteLine(parent.Status);
 7     Thread.Sleep(4000);
 8     Console.WriteLine(parent.Status);
 9     Console.ReadKey();
10 }
11 static void ParentTask()
12 {
13     Console.WriteLine("任務Id:"+Task.CurrentId);
14     var child = new Task(ChildTask);
15     child.Start();
16     Thread.Sleep(1000);
17     Console.WriteLine("父級子任務已開始運行");
18 }
19 static void ChildTask()
20 {
21     Console.WriteLine("子任務開始");
22     Thread.Sleep(5000);
23     Console.WriteLine("子任務結束");
24 }

 

 

(四)取消架構

.NET4.5包含一個取消架構,允許以標準方式取消長時間運行的任務。取消架構基於協作行為,它不是強制的。長時間運行的任務會檢查它是否被取消,並返回控制權。支持取消的方法接受一個CancellationToken參考。

 

1、Parallel.For()方法的取消

 1 var cts = new CancellationTokenSource();
 2 cts.Token.Register(() => Console.WriteLine("*** token canceled"));
 3 
 4  
 5 //在500毫秒以後發送取消指令
 6 cts.CancelAfter(500);
 7 try
 8 {
 9     var result = Parallel.For(0, 100, new ParallelOptions() { CancellationToken = cts.Token, }, x =>
10     {
11         Console.WriteLine("{0}次迴圈開始", x)
12         int sum = 0;
13         for (int i = 0; i < 100; i++)
14         {
15             Thread.Sleep(2);
16             sum += i;
17         }
18         Console.WriteLine("{0}次迴圈結束", x);
19     });
20 }
21 catch (OperationCanceledException ex)
22 {
23     Console.WriteLine(ex.Message);
24 }

使用.NET4.5中的一個新方法CancelAfter,在500毫秒後取消標記。在For()迴圈的實現代碼內部,Parallel類驗證CanceledToken的結果,並取消操作。一旦取消操作,For()方法就拋出一個OperationCanceledException類型的異常。

 

2、任務的取消

同樣的取消模式也可以用於任務。

 1 var cts = new CancellationTokenSource();
 2 cts.Token.Register(() => Console.WriteLine("*** token canceled"));
 3 
 4 //在500毫秒以後發送取消指令
 5 cts.CancelAfter(500);
 6  
 7 Task t1 = Task.Run(()=> {
 8     Console.WriteLine("任務進行中...");
 9     for (int i = 0; i < 20; i++)
10     {
11         Thread.Sleep(100);
12         CancellationToken token = cts.Token;
13         if (token.IsCancellationRequested)
14         {
15             Console.WriteLine("已發送取消請求,取消請求來自當前任務");
16             token.ThrowIfCancellationRequested();
17             break;
18         }
19         Console.WriteLine("迴圈中...");
20     }
21     Console.WriteLine("任務結束沒有取消");
22 });
23 try
24 {
25     t1.Wait();
26 }
27 catch (AggregateException ex)
28 {
29     Console.WriteLine("異常:{0}, {1}",ex.GetType().Name,ex.Message);
30     foreach (var innerException in ex.InnerExceptions)
31     {
32         Console.WriteLine("異常:{0}, {1}", ex.InnerException.GetType().Name, ex.InnerException.Message);
33     }
34 }

 

 

(五)線程池

int nWorkerThreads;
int nCompletionPortThreads;
ThreadPool.GetMaxThreads(out nWorkerThreads, out nCompletionPortThreads);
Console.WriteLine("線程池中輔助線程的最大數目:{0}, 線程池中非同步 I/O 線程的最大數目:{1}",nWorkerThreads,nCompletionPortThreads);
for (int i = 0; i < 5; i++)
{
    ThreadPool.QueueUserWorkItem(JobForAThread);
}
Thread.Sleep(3000);

需要註意的是:線程池中的所有線程都是後臺線程,不可設置線程優先順序、名稱,隨著前臺線程的結束而結束,只適用於短時間任務。

 

(六)Thread類

該類允許創建前臺線程,以及設置線程的優先順序。

 

1、給線程傳遞數據

static void Main(string[] args)
{
    var t2 = new Thread(ThreadMainWithParameter);
    t2.Start("參數字元串");
 
    Console.ReadKey();
}
static void ThreadMainWithParameter(object message)
{
    Console.WriteLine("運行主線程,接受參數:" + message.ToString());
}

如果使用了ParameterizedThreadStart委托,線程的入口點必須有一個object類型的參數,且返回類型為void。

 

2、後臺線程

只要有一個前臺線程在運行,應用程式的進程就在運行。如果多個前臺線程在運行,而Main()方法結束了,應用程式的進程就仍然是激活的,直到所有前臺線程完成其任務為止。

預設情況下,用Thread類創建的線程是前臺線程,線程池中的線程是後臺線程。Thread類創建線程時,可以設置IsBackground屬性來確定創建前臺還是後臺線程。

static void Main(string[] args)
{
    var t1 = new Thread(ThreadMain) { Name = "MyNewThread", IsBackground = false };
    t1.Start();
    Console.WriteLine("主線程現在結束");
    Console.ReadKey();
}

 
private static void ThreadMain()
{
    Console.WriteLine(Thread.CurrentThread.Name+"線程開始運行");
    Thread.Sleep(3000);
    Console.WriteLine(Thread.CurrentThread.Name+"線程結束");
}

在通過Thread類創建線程的時候,設置IsBackground屬性為false,也就是創建一個前臺線程。這種情況下在主線程結束時,t1不會結束。但如果將IsBackground設置為true,則會隨著主線程的結束而結束。

 

3、線程的優先順序

給線程指定優先順序,就可以影響系統對線程的調度順序。在Thread類中,可以設置Priority屬性,以影響線程的基本優先順序。

 

4、控制線程

讀取Tread的ThreadState屬性,可以獲取線程的狀態。

Thread的Start()方法創建線程,線程狀態為UnStarted;

線程調度器選擇運行後,線程轉檯改變為Running;

Thread.Sleep()方法會使線程處於WaitSleepJoin;

Thread類的Abort()方法,觸發ThreadAbortException類型的異常,線程狀態會變為AbortedRequested,如果沒有重置終止則變為Aborted;

Thread.ResetAbort()方法可以讓線程在觸發ThreadAbortException異常後繼續運行;

Thread類的Join()會停止當前線程,等待加入的線程完成為止,此時線程狀態為WaitSleepJoin。

 

 

(七)線程問題

1、爭用條件

如果兩個或多個線程訪問相同的對象,並且對共用狀態的訪問沒有同步,就會出現爭用條件。要避免該問題,可以鎖定共用對象。這可以通過lock語句鎖定線上程中共用的state變數。

private static readonly object locker = new object();

public void ChnageI(int i)
{
    lock (locker)
    {
        if (i == 0)
        {
            i++;
            Console.WriteLine(i == 1);
        }
        i = 0;
    }
}

 

2、死鎖

由於兩個線程都在等待對方,就出現了死鎖,線程將無線等待下去。為了避免這個問題,可以在應用程式的體系架構中,從一開始就設計好鎖定的順序,也可以為鎖定定義超時時間。

 

 

(八)同步

共用數據必須使用同步技術,確保一次只有一個線程訪問和改變共用狀態。可以使用lock語句、Interlocked類和Monitor類進行進程內部的同步。Mutex類、Event類、SemaphoreSlim類和ReaderWriterLockSlim類提供了多個進程之間的線程同步。

 

1、lock語句和線程安全

lock語句是設置鎖定和解除鎖定的一種簡單方式。

在沒有使用lock語句的情況下,多個線程操作共用數據,最後得到的結果沒有一個會正確。

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     {
 5         for (int j = 0; j < 5; j++)
 6         {
 7             int numTasks = 20;
 8             var state = new SharedState();
 9             var tasks = new Task[numTasks];
10             for (int i = 0; i < numTasks; i++)
11             {
12                 tasks[i] = Task.Run(() => { new Job(state).DoTheJob(); });
13             }
14  
15             for (int i = 0; i < numTasks; i++)
16             {
17                 tasks[i].Wait();
18             }
19             Console.WriteLine("最後結果:{0}", state.State);
20         }
21         Console.ReadKey();
22     }
23 }
24 
25 public class SharedState
26 {
27     public int State { get; set; }
28 }
29 
30 public class Job
31 {
32     SharedState sharedState;
33     public Job(SharedState sharedState)
34     {
35         this.sharedState = sharedState;
36     }
37     public void DoTheJob()
38     {
39         for (int i = 0; i < 50000; i++)
40         {
41             sharedState.State += 1;
42         }
43     }
44 }

使用lock語句,修改DoTheJob()方法,現在才能獲得正確的結果。

private readonly object syncRoot = new object();

public void DoTheJob()
{
    for (int i = 0; i < 50000; i++)
    {
        lock (syncRoot)
        {
            sharedState.State += 1;
        }
    }
}

 

2、Interlocked類

Interlocked類用於使變數的簡單語句原子化。

public int State
{
    get
    {
        lock (this)
        {
            return ++state;
        }
    }
}

public int State
{
    get
    {
        return Interlocked.Increment(ref state);
    }
}

使用Interlocked類可以更快。

 

3、Monitor類

lock語句由C#編譯器解析為使用Monitor類。

lock (syncRoot)
{
    //代碼
}
//C#編譯器將lock語句解析為
Monitor.Enter(syncRoot);
try
{
    //代碼
}
finally
{
    Monitor.Exit(syncRoot);
}

Monitor類相對於lock語句的優點是:可以通過調用TryEnter()方法添加一個等待被鎖定的超時值。

bool lockTaken = false;
Monitor.TryEnter(syncRoot, 1000, ref lockTaken);
if (lockTaken)
{
    //獲取鎖後操作
    try
    {
        //代碼
    }
    finally
    {
        Monitor.Exit(syncRoot);
    }
}
else
{
    //沒有獲取鎖的操作
}

 

4、SpinLock結構

相對於Monitor垃圾回收導致過高的系統開銷,使用SpinLock結構就能有效降低系統開銷。SpinLock的使用方式與Monitor非常相似,但因為SpinLock是結構所以在把變數賦值為另一個變數會創建一個副本。

 

5、WaitHandle基類

WaitHandle是一個抽象基類,用於等待一個信號的設置。可以等待不同的信號,因為WaitHandle是一個基類,可以從中派生一些類。

 

6、Mutex類

Mutex(mutual exclusion,互斥)是.NET Framework中提供跨多個線程同步訪問的一個類。

在Mutex類的構造函數中,可以指定互斥是否最初由主調線程擁有,定義互斥的名稱,獲得互斥是否存在的信息。

bool createdNew;
var mutex = new Mutex(false, "MyMutex", out createdNew);

系統可以識別有名稱的互斥,可以使用它來禁止應用程式啟動兩次。

bool createdNew;
var mutex = new Mutex(false, "MyMutex", out createdNew);
if (!createdNew)
{
    Console.WriteLine("每次只能啟動一個應用程式");
    Environment.Exit(0);
}
Console.WriteLine("運行中...");

 

7、Semaphore類

信號量是一種計數的互斥鎖。如果需要限制可以訪問可用資源的線程數,信號量就很有用。

.NET4.5為信號量功能提供了兩個類Semaphore和SemaphoreSlim。Semaphore類可以命名,使用系統範圍內的資源,允許在不同進程之間同步。SemaphoreSlim類是對較短等待時間進行了優化的輕型版本。

 1 static void Main(string[] args)
 2 {
 3     int taskCount = 6;
 4     int semaphoreCount = 3;
 5     var semaphore = new SemaphoreSlim(semaphoreCount, semaphoreCount);
 6     var tasks = new Task[taskCount];
 7 
 8  
 9     for (int i = 0; i < taskCount; i++)
10     {
11         tasks[i] = Task.Run(() =>
12         {
13             TaskMain(semaphore);
14         });
15     }
16 
17     Task.WaitAll(tasks);
18  
19     Console.WriteLine("所有任務已結束");
20     Console.ReadKey();
21 }
22 
23  
24 private static void TaskMain(SemaphoreSlim semaphore)
25 {
26     bool isCompleted = false;
27     while (!isCompleted)
28     {
29         if (semaphore.Wait(600))
30         {
31             try
32             {
33                 Console.WriteLine("任務{0}鎖定了信號", Task.CurrentId);
34                 Thread.Sleep(2000);
35             }
36             finally
37             {
38                 Console.WriteLine("任務{0}釋放了信號", Task.CurrentId);
39                 semaphore.Release();
40                 isCompleted = true;
41             }
42         }
43         else
44         {
45             Console.WriteLine("任務{0}超時,等待再次執行", Task.CurrentId);
46         }
47     }
48 }

 

8、Events類

與互斥和信號量對象一樣,事件也是一個系統範圍內的資源同步方法。為了從托管代碼中使用系統事件,.NET Framework在System.Threading名稱空間中提供了ManualResetEvent、AutoResetEvent、ManualResetEventSlim和CountdownEvent類。

C#中event關鍵字與這裡的event類沒有任何關係。

 

9、Barrier類

對於同步,Barrier類非常適用於其中

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • K9F2G08U0C是samsun出產的FLASH,容量為256MB 頁--Page: (2K + 64)Byte 塊--Block: (128K + 4K)Byte 128 / 2 = 64 Page 256M / 128 K = 2048 Block 現在以第25塊的30頁中的24byte為例 ...
  • 實驗環境: 本地windows 8.1 遠程連接工具 SecureCRT 7.3 Linux發行版本 CentOS 6.7 x86_64位Linux系統,內核的版本為2.6.32-573 mysql版本 mysql-5.5.32 1.1 MySQL資料庫字元集介紹 字元集就是一套文字元號及編碼、比較 ...
  • 1.首先保證虛擬機的網路適配器為NAT模式 2.設置虛擬機的“編輯”--》“虛擬網路編輯器”中的VMnet8的DHCP的設置兩個選項都勾選上。 3.設置物理主機,保證虛擬網關的IP地址為自動獲取;同時本地連接也設置為自動獲取 4.開啟物理主機的VMware DHCP Service 和VMware ...
  • C盤在使用過程中,內容會越來越多,剩餘空間越來越小。如何清理出更多空間呢?以windows7為例 cleanmgr windows自帶的磁碟清理工具。在運行視窗中執行cleanmgr 如下圖所示,工具會提示可以清空的空間大小,從幾十M到幾十G,都有可能。 減小虛擬記憶體,或將虛擬記憶體移動到其它分區 w ...
  • 如用戶表和電話表,要求搜索時可以模糊查詢姓名和號碼。都可以找到包含該字元的所有用戶。 電話表不能用where去查詢。只能用Any去查詢電話表。 原理不懂。初學。我也是看了 http://www.cnblogs.com/zhaopei/p/5721789.html 這文章才明白怎麼用的~~~ ...
  • 修改app_start/webapiconfig.cs using System.Web.Http; using System.Web.Routing; using Ninject; using TxMobile.Filters; using TxMobile.Models; using WebAp... ...
  • 前言 我們知道目前 .NET Core 還不支持 SMTP 協議,當我麽在使用到發送郵件功能的時候,需要藉助於一些第三方組件來達到目的,今天給大家介紹兩款開源的郵件發送組件,它們分別是 " MailKit " 和 " FluentEmail " , 下麵我對它們分別進行介紹。 MailKit 在 A ...
  • BusterWood.Channels是一個在C#上實現的通道的開源庫。通過使用這個類庫,我們可以在C#語言中實現類似golang和goroutine的通道編程方式。在這裡我們介紹3個簡單的通道的例子。 通過通道發送消息(https://gobyexample.com/channels): stat ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...