那些年,我們一起追尋的非同步程式設計
術語:
APM 非同步程式設計模型,Asynchronous Programming Model
EAP 基於事件的非同步程式設計模式,Event-based Asynchronous Pattern
TAP 基於任務的非同步程式設計模式,Task-based Asynchronous Pattern
TPL 任務並行庫,Task Parallel Library
現在我給這個系列整個目錄和做個簡單介紹。
“概要 + 目錄”整理
C#語言是微軟於2000年釋出,基於.NET Framewrok框架的、物件導向的高階語言。經過近十三年的發展,經歷了5次大的升級,目前最新版本為C#5.0(對應於.NET Framework 4.5)。其中每個版本釋出都是有一個“主題”。即:C#1.0託管程式碼→C#2.0泛型→C#3.0LINQ→C#4.0動態語言→C#5.0非同步程式設計。這系列既是針對“非同步程式設計”所寫。
C#版本 | .NET 版本 | Visual Studio 版本 | 特性描述 |
---|---|---|---|
C# 1.0 | .NET 1.0/1.1 | VS 2002/2003 | C#的第一個正式發行版本。微軟的團隊從無到有創造了一種語言,專門為.NET程式設計提供支援 |
C# 2.0 | .NET 2.0 | VS 2005 | C#語言開始支援泛型,.NET Framework 2.0新增了支援泛型的庫 |
C# 2.0 | .NET 3.0 | VS 2005 | 新增了一套API來支援分散式通訊(Windows Communication Foundation— WCF)、富客戶端表示(Windows Presentation Foundation)、工作流(Windows Workflow—WF)以及Web身份驗證(Cardspaces) |
C# 3.0 | .NET 3.5 | VS 2008 | 新增了對LINQ的支援,對用於集合程式設計的API進行了大幅改進。.NET Framework 3.5對原有的API進行了擴充套件,從而支援了LINQ |
C# 4.0 | .NET 4.0 | VS 2010 | 新增了動態型別(dynamic)的支援,引入了新的輕量級執行緒同步基元及新的非同步程式設計類庫TPL |
C# 5.0 | .NET 4.5 | VS 2012 | 改進並擴充套件了.NET4.0中引入的TPL類庫,並引入async和await關鍵字輕鬆構建非同步方法。 |
1. 我的非同步程式設計整理
資料整理路線:執行緒—-執行緒池—-執行緒同步—-並行任務—-三種非同步程式設計模型。首先了解最基礎的執行緒(Thread
類),再進一步明白執行緒管理器(ThreadPool
類)。因為多個工作項之間可能出現並行執行,會造成對共享資源的訪問問題,所以引入執行緒同步基元來讓共享資源得到合理使用。最後介紹.NET4.0新引入並在.NET4.5中得到優化和擴充套件的TPL(任務並行庫),並結合C# 5.0中新引入的async
和await
關鍵字輕鬆構建非同步方法。詳細如下:
非同步程式設計:執行緒概述及使用
非同步程式設計:使用執行緒池管理執行緒
非同步程式設計:執行緒同步基元物件
非同步程式設計:輕量級執行緒同步基元物件
非同步程式設計:.NET4.5 資料並行
非同步程式設計:非同步程式設計模型 (APM)
非同步程式設計:基於事件的非同步程式設計模式(EAP)
非同步程式設計:.NET 4.5 基於任務的非同步程式設計模型(TAP)
非同步程式設計:IAsyncResult非同步程式設計模型 (APM)
大部分開發人員,在開發多執行緒應用程式時,都是使用ThreadPool
的QueueUserWorkItem
方法來發起一次簡單的非同步操作。然而,這個技術存在許多限制。最大的問題是沒有一個內建的機制讓你知道操作在什麼時候完成,也沒有一個機制在操作完成時獲得一個返回值。為了克服這些限制(並解決其他一些問題),Microsoft引入了三種非同步程式設計模式:
.NET1.0非同步程式設計模型 (APM),基於
IAsyncResult
介面實現。.NET2.0基於事件的非同步程式設計模式(EMP),基於事件實現。
.NET4.X基於任務的非同步程式設計模式(TPL),新型非同步程式設計模式,對於.NET4.0之後的非同步構造都推薦使用此模式
儘管在新的設計上我們推薦都使用“.NET4.0基於任務的程式設計模式”,但我還是計劃整理出舊版的非同步程式設計模型,因為:
在一些特殊場合下我們可能覺得一種模式更適合;
可以更充分認識三種模式之間的優劣,便於選擇;
很多遺留的程式碼包含了舊的設計模式;
等等…
示例下載:非同步程式設計:IAsyncResult非同步程式設計模型.rar
IAsyncResult
設計模式—-規範概述
使用IAsyncResult
設計模式的非同步操作是通過名為 Begin***
和 End***
的兩個方法來實現的,這兩個方法分別指代開始和結束非同步操作。例如,FileStream
類提供BeginRead
和EndRead
方法來從檔案非同步讀取位元組。這兩個方法實現了 Read
方法的非同步版本。
在呼叫 Begin***
後,應用程式可以繼續在呼叫執行緒上執行指令,同時非同步操作在另一個執行緒上執行。(如果有返回值還應呼叫 End***
來獲取操作的結果)。
1)Begin***
a)Begin***
方法帶有該方法的同步版本簽名中宣告的任何引數。
b)Begin***
方法簽名中不包含任何輸出引數。方法簽名最後兩個引數的規範是:第一個引數定義一個AsyncCallback委託,此委託引用在非同步操作完成時呼叫的方法。第二個引數是一個使用者定義的物件。此物件可用來向非同步操作完成時為AsyncCallback委託方法傳遞應用程式特定的狀態資訊(eg:可通過此物件在委託中訪問End*** 方法)。另外,這兩個引數都可以傳遞null。
c)返回IAsyncResult
物件。
// 表示非同步操作的狀態。
[ComVisible(true)]
public interface IAsyncResult
{
// 獲取使用者定義的物件,它限定或包含關於非同步操作的資訊。
object AsyncState { get; }
// 獲取用於等待非同步操作完成的System.Threading.WaitHandle,待非同步操作完成時獲得訊號。
WaitHandle AsyncWaitHandle { get; }
// 獲取一個值,該值指示非同步操作是否同步完成。
bool CompletedSynchronously { get; }
// 獲取一個值,該值指示非同步操作是否已完成。
bool IsCompleted { get; }
}
// 常用委託宣告(我後面示例是使用了自定義的帶ref引數的委託)
public delegate void AsyncCallback(IAsyncResult ar)
2)End***
a) End***
方法可結束非同步操作,如果呼叫 End***
時,IAsyncResult
物件表示的非同步操作還未完成,則 End***
將在非同步操作完成之前阻塞呼叫執行緒。
b) End***
方法的返回值與其同步副本的返回值型別相同。End***
方法帶有該方法同步版本的簽名中宣告的所有out
和 ref
引數以及由BeginInvoke
返回的IAsyncResult
,規範上 IAsyncResult
引數放最後。
i.要想獲得返回結果,必須呼叫的方法;
ii.若帶有out 和 ref 引數,實現上委託也要帶有out
和ref
引數,以便在回撥中獲得對應引用傳參值做相應邏輯;
3) 總是呼叫 End***
() 方法,而且只呼叫一次
以下理由都是針對“I/O限制”的非同步操作提出。然而,對於計算限制的非同步操作,儘管都是使用者程式碼,但還是推薦遵守此規則。
I/O限制的非同步操作:比如像帶
FileOptions.Asynchronous
標識的FileStream
,其BeginRead()
方法向Windows
傳送一個I/O請求包(I/O Request Packet,IRP
)後方法不會阻塞執行緒而是立即返回,由Windows
將IRP
傳送給適當的裝置驅動程式,IRP
中包含了為BeginRead()
方法傳入的回撥函式,待硬體裝置處理好IRP
後,會將IRP
的委託排隊到CLR
的執行緒池佇列中。
必須呼叫End***
方法,否則會造成資源的洩露。有的開發人員寫程式碼呼叫Begin***
方法非同步執行I/O
限制後就不需要進行任何處理了,所以他們不關心End***
方法的呼叫。但是,出於以下兩個原因,End***
方法是必須呼叫的:
a) 在非同步操作時,對於I/O限制操作,
CLR
會分配一些內部資源,操作完成時,CLR
繼續保留這些資源直至End***
方法被呼叫。如果一直不呼叫End***
,這些資源會直到程式終止時才會被回收。(End***
方法設計中常常包含資源釋放)b) 發起一個非同步操作時,實際上並不知道該操作最終是成功還是失敗(因為操作由硬體在執行)。要知道這一點,只能通過呼叫
End***
方法,檢查它的返回值或者看它是否丟擲異常。
另外,需要注意的是I/O限制的非同步操作完全不支援取消(因為操作由硬體執行),但可以設定一個標識,在完成時丟棄結果來模擬取消行為。
現在我們清楚了IAsyncResult
設計模式的設計規範,接下來我們再通過IAsyncResult
非同步程式設計模式的三個經典場合來加深理解。
一、基於IAsyncResult
構造一個非同步API
現在來構建一個IAsyncResult
的類,並且實現非同步呼叫。
// 帶ref引數的自定義委託
public delegate void RefAsyncCallback(ref string resultStr, IAsyncResult ar);
public class CalculateAsyncResult : IAsyncResult
{
private int _calcNum1;
private int _calcNum2;
private RefAsyncCallback _userCallback;
public CalculateAsyncResult(int num1, int num2, RefAsyncCallback userCallback, object asyncState)
{
this._calcNum1 = num1;
this._calcNum2 = num2;
this._userCallback = userCallback;
this._asyncState = asyncState;
// 非同步執行操作
ThreadPool.QueueUserWorkItem((obj) => { AsyncCalculate(obj); }, this);
}
#region IAsyncResult介面
private object _asyncState;
public object AsyncState { get { return _asyncState; } }
private ManualResetEvent _asyncWaitHandle;
public WaitHandle AsyncWaitHandle
{
get
{
if (this._asyncWaitHandle == null)
{
ManualResetEvent event2 = new ManualResetEvent(false);
Interlocked.CompareExchange<ManualResetEvent>(ref this._asyncWaitHandle, event2, null);
}
return _asyncWaitHandle;
}
}
private bool _completedSynchronously;
public bool CompletedSynchronously { get { return _completedSynchronously; } }
private bool _isCompleted;
public bool IsCompleted { get { return _isCompleted; } }
#endregion
/// <summary>
///
/// 儲存最後結果值
/// </summary>
public int FinnalyResult { get; set; }
/// <summary>
/// End方法只應呼叫一次,超過一次報錯
/// </summary>
public int EndCallCount = 0;
/// <summary>
/// ref引數
/// </summary>
public string ResultStr;
/// <summary>
/// 非同步進行耗時計算
/// </summary>
/// <param name="obj">CalculateAsyncResult例項本身</param>
private static void AsyncCalculate(object obj)
{
CalculateAsyncResult asyncResult = obj as CalculateAsyncResult;
Thread.SpinWait(1000);
asyncResult.FinnalyResult = asyncResult._calcNum1 * asyncResult._calcNum2;
asyncResult.ResultStr = asyncResult.FinnalyResult.ToString();
// 是否同步完成
asyncResult._completedSynchronously = false;
asyncResult._isCompleted = true;
((ManualResetEvent)asyncResult.AsyncWaitHandle).Set();
if (asyncResult._userCallback != null)
asyncResult._userCallback(ref asyncResult.ResultStr, asyncResult);
}
}
public class CalculateLib
{
public IAsyncResult BeginCalculate(int num1, int num2, RefAsyncCallback userCallback, object asyncState)
{
CalculateAsyncResult result = new CalculateAsyncResult(num1, num2, userCallback, asyncState);
return result;
}
public int EndCalculate(ref string resultStr, IAsyncResult ar)
{
CalculateAsyncResult result = ar as CalculateAsyncResult;
if (Interlocked.CompareExchange(ref result.EndCallCount, 1, 0) == 1)
{
throw new Exception("End方法只能呼叫一次。");
}
result.AsyncWaitHandle.WaitOne();
resultStr = result.ResultStr;
return result.FinnalyResult;
}
public int Calculate(int num1, int num2, ref string resultStr)
{
resultStr = (num1 * num2).ToString();
return num1 * num2;
}
}
使用上面通過IAsyncResult
設計模式實現的帶ref
引用引數的非同步操作,我將展示三種阻塞式響應非同步呼叫和一種無阻塞式委託響應非同步呼叫。即:
1.執行非同步呼叫後,若我們需要控制後續執行程式碼在非同步操作執行完之後執行,可通過下面三種方式阻止其他工作:(當然我們不推薦你阻塞執行緒或輪詢浪費CPU時間)
a)
IAsyncResult
的AsyncWaitHandle
屬性,待非同步操作完成時獲得訊號。b) 通過
IAsyncResult
的IsCompleted
屬性進行輪詢。c) 呼叫非同步操作的
End***
方法。
/// <summary>
/// APM 阻塞式非同步響應
/// </summary>
public class Calculate_For_Break
{
public static void Test()
{
CalculateLib cal = new CalculateLib();
// 基於IAsyncResult構造一個非同步API (回撥引數和狀態物件都傳遞null)
IAsyncResult calculateResult = cal.BeginCalculate(123, 456, null, null);
// 執行非同步呼叫後,若我們需要控制後續執行程式碼在非同步操作執行完之後執行,可通過下面三種方式阻止其他工作:
// 1、IAsyncResult 的 AsyncWaitHandle 屬性,帶非同步操作完成時獲得訊號。
// 2、通過 IAsyncResult 的 IsCompleted 屬性進行輪詢。通過輪詢還可實現進度條功能。
// 3、呼叫非同步操作的 `End***` 方法。
// ***********************************************************
// 1、calculateResult.AsyncWaitHandle.WaitOne();
// 2、while (calculateResult.IsCompleted) { Thread.Sleep(1000); }
// 3、
string resultStr = string.Empty;
int result = cal.EndCalculate(ref resultStr, calculateResult);
}
}
2.執行非同步呼叫後,若我們不需要阻止後續程式碼的執行,那麼我們可以把非同步執行操作後的響應放到回撥中進行。(推薦使用無阻塞式回撥模式)
/// <summary>
/// APM 回撥式非同步響應
/// </summary>
public class Calculate_For_Callback
{
public static void Test()
{
CalculateLib cal = new CalculateLib();
// 基於IAsyncResult構造一個非同步API
IAsyncResult calculateResult = cal.BeginCalculate(123, 456, AfterCallback, cal);
}
/// <summary>
/// 非同步操作完成後做出響應
/// </summary>
private static void AfterCallback(ref string resultStr, IAsyncResult ar)
{
// 執行非同步呼叫後,若我們不需要阻止後續程式碼的執行,那麼我們可以把非同步執行操作後的響應放到回撥中進行。
CalculateLib cal = ar.AsyncState as CalculateLib;
cal.EndCalculate(ref resultStr, ar);
// 再根據resultStr值做邏輯。
}
}
二、使用委託進行非同步程式設計
對於委託,編譯器會為我們生成同步呼叫方法“invoke
”以及非同步呼叫方法“BeginInvoke
”和“EndInvoke
”。對於非同步呼叫方式,公共語言執行庫 (CLR
) 將對請求進行排隊並立即返回到呼叫方,由執行緒池的執行緒排程目標方法並與提交請求的原始執行緒並行執行,為BeginInvoke()
方法傳入的回撥方法也將在同一個執行緒上執行。
非同步委託是快速為方法構建非同步呼叫的方式,它基於IAsyncResult
設計模式實現的非同步呼叫,即,通過BeginInvoke
返回IAsyncResult
物件;通過EndInvoke
獲取結果值。
示例:
上節的CalculateLib
類中的同步方法以及所要使用到的委託如下:
// 帶ref引數的自定義委託
public delegate int AsyncInvokeDel(int num1, int num2, ref string resultStr);
public int Calculate(int num1, int num2, ref string resultStr)
{
resultStr = (num1 * num2).ToString();
return num1 * num2;
}
然後,通過委託進行同步或非同步呼叫:
/// <summary>
/// 使用委託進行非同步呼叫
/// </summary>
public class Calculate_For_Delegate
{
public static void Test()
{
CalculateLib cal = new CalculateLib();
// 使用委託進行同步或非同步呼叫
AsyncInvokeDel calculateAction = cal.Calculate;
string resultStrAction = string.Empty;
// int result1 = calculateAction.Invoke(123, 456);
IAsyncResult calculateResult1 = calculateAction.BeginInvoke(123, 456, ref resultStrAction, null, null);
int result1 = calculateAction.EndInvoke(ref resultStrAction, calculateResult1);
}
}
三、多執行緒操作控制元件
訪問 Windows
窗體控制元件本質上不是執行緒安全的。如果有兩個或多個執行緒操作某一控制元件的狀態,則可能會迫使該控制元件進入一種不一致的狀態。還可能出現其他與執行緒相關的 bug
,包括爭用情況和死鎖。確保以執行緒安全方式訪問控制元件非常重要。
不過,在有些情況下,您可能需要多執行緒呼叫控制元件的方法。.NET Framework 提供了從任何執行緒操作控制元件的方式:
1.非安全方式訪問控制元件(此方式請永遠不要再使用)
多執行緒訪問視窗中的控制元件,可以在視窗的建構函式中將Form
的CheckForIllegalCrossThreadCalls
靜態屬性設定為false
。
// 獲取或設定一個值,該值指示是否捕獲對錯誤執行緒的呼叫,
// 這些呼叫在除錯應用程式時訪問控制元件的System.Windows.Forms.Control.Handle屬性。
// 如果捕獲了對錯誤執行緒的呼叫,則為 true ;否則為 false 。
public static bool CheckForIllegalCrossThreadCalls { get; set; }
2.安全方式訪問控制元件
原理:從一個執行緒封送呼叫並跨執行緒邊界將其傳送到另一個執行緒,並將呼叫插入到建立控制元件執行緒的訊息佇列中,當控制元件建立執行緒處理這個訊息時,就會在自己的上下文中執行傳入的方法。(此過程只有呼叫執行緒和建立控制元件執行緒,並沒有建立新執行緒)
注意:從一個執行緒封送呼叫並跨執行緒邊界將其傳送到另一個執行緒會耗費大量的系統資源,所以應避免重複呼叫其他執行緒上的控制元件。
1)使用
BackgroundWork
後臺輔助執行緒控制元件方式(詳見:基於事件的非同步程式設計模式(EMP))。2)結合
TaskScheduler.FromCurrentSynchronizationContext()
和Task
實現。3)捕獲執行緒上下文
ExecuteContext
,並呼叫ExeceteContext.Run()
靜態方法在指定的執行緒上下文中執行。(詳見:執行上下文)4)使用
Control
類上提供的Invoke
和BeginInvoke
方法。5)在WPF應用程式中可以通過WPF提供的
Dispatcher
物件提供的Invoke
方法、BeginInvoke
方法來完成跨執行緒工作。
因本文主要解說IAsyncResult
非同步程式設計模式,所以只詳細分析Invoke
和BeginInvoke
跨執行緒訪問控制元件方式。
Control
類實現了ISynchronizeInvoke
介面,提供了Invoke
和BeginInvoke
方法來支援其它執行緒更新GUI介面控制元件的機制。
public interface ISynchronizeInvoke
{
// 獲取一個值,該值指示呼叫執行緒是否與控制元件的建立執行緒相同。
bool InvokeRequired { get; }
// 在控制元件建立的執行緒上非同步執行指定委託。
AsyncResult BeginInvoke(Delegate method, params object[] args);
object EndInvoke(IAsyncResult asyncResult);
// 在控制元件建立的執行緒上同步執行指定委託。
object Invoke(Delegate method, params object[] args);
}
1)Control
類的 Invoke
,BeginInvoke
內部實現如下:
a)
Invoke
(同步呼叫)先判斷控制元件建立執行緒與當前執行緒是否相同,相同則直接呼叫委託方法;否則使用Win32API
的PostMessage
非同步執行,但是Invoke
內部會呼叫IAsyncResult.AsyncWaitHandle
等待執行完成。b)
BeginInvoke
(非同步呼叫)使用Win32API
的PostMessage
非同步執行,並且返回IAsyncResult
物件。
UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle)
, threadCallbackMessage, IntPtr.Zero, IntPtr.Zero);
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern bool PostMessage(HandleRefhwnd, intmsg, IntPtrwparam, IntPtrlparam);
PostMessage
是windows api,用來把一個訊息傳送到一個視窗的訊息佇列。這個方法是非同步的,也就是該方法封送完畢後馬上返回,不會等待委託方法的執行結束,呼叫者執行緒將不會被阻塞。(對應同步方法的windows api是:SendMessage();
訊息佇列裡的訊息通過呼叫GetMessage
和PeekMessage
取得)
2)InvokeRequired
獲取一個值,該值指示呼叫執行緒是否與控制元件的建立執行緒相同。內部關鍵如下:
Int windowThreadProcessId = SafeNativeMethods.GetWindowThreadProcessId(ref2, out num);
Int currentThreadId = SafeNativeMethods.GetCurrentThreadId();
return (windowThreadProcessId != currentThreadId);
即返回“通過GetWindowThreadProcessId
功能函式得到建立指定視窗執行緒的標識和建立視窗的程式的識別符號與當前執行緒Id進行比較”的結果。
3)示例(詳見示例檔案)
在使用的時候,我們使用 this.InvokeRequired
屬性來判斷是使用Invoke
或BeginInvoke
還是直接呼叫方法。
private void InvokeControl(object mainThreadId)
{
if (this.InvokeRequired)
{
this.Invoke(new Action<String>(ChangeText), "InvokeRequired = true.改變控制元件Text值");
//this.textBox1.Invoke(new Action<int>(InvokeCount), (int)mainThreadId);
}
else
{
ChangeText("在建立控制元件的執行緒上,改變控制元件Text值");
}
}
private void ChangeText(String str)
{
this.textBox1.Text += str;
}
注意,在InvokeControl
方法中使用 this.Invoke(Delegate del)
和使用 this.textBox1.Invoke(Delegate del)
效果是一樣的。因為在執行Invoke
或BeginInvoke
時,內部首先呼叫 FindMarshalingControl()
進行一個迴圈向上回溯,從當前控制元件開始回溯父控制元件,直到找到最頂級的父控制元件,用它作為封送物件。也就是說 this.textBox1.Invoke(Delegate del)
會追溯到和 this.Invoke(Delegate del)
一樣的起點。(子控制元件的建立執行緒一定是建立父控制元件的執行緒,所以這種追溯不會導致將呼叫封送到錯誤的目的執行緒)
4)異常資訊:”在建立視窗控制程式碼之前,不能在控制元件上呼叫 Invoke
或 BeginInvoke
”
a) 可能是在窗體還未構造完成時,在建構函式中非同步去呼叫了
Invoke
或BeginInvoke
;b) 可能是使用輔助執行緒建立一個視窗並用
Application.Run()
去建立控制程式碼,在控制程式碼未建立好之前呼叫了Invoke
或BeginInvoke
。(此時新建的視窗相當於開了另一個程式,並且為新視窗關聯的輔助執行緒開啟了訊息迴圈機制),類似下面程式碼:
new Thread((ThreadStart)delegate
{
WaitBeforeLogin = new Form2();
Application.Run(WaitBeforeLogin);
}).Start();
解決方案:在呼叫Invoke
或 BeginInvoke
之前輪詢檢查視窗的IsHandleCreated
屬性。
// 獲取一個值,該值指示控制元件是否有與它關聯的控制程式碼。
public bool IsHandleCreated { get; }
while (!this.IsHandleCreated) { …… }
本節到此結束,本節主要講了非同步程式設計模式之一“非同步程式設計模型(APM)”,是基於IAsyncResult
設計模式實現的非同步程式設計方式,並且構建了一個繼承自IAsyncResult
介面的示例,及展示了這種模式在委託及跨執行緒訪問控制元件上的經典應用。下一節中,我將為大家介紹基於事件的程式設計模型……
相關文章
- 那些年我們一起追過的大佬
- 那些年我們一起追過的高深術語
- 那些年,我們一起誤解過的RESTREST
- 那些年我們一起踩過的Dubbo坑
- 四萬字歌詞分析:那些年,我們一起追的五月天到底在唱什麼?
- SQL Server DBA:那些年,我們一起用過的工具FASQLServer
- Python:那些年我們遇到的坑Python
- 回溯演算法 | 追憶那些年曾難倒我們的八皇后問題演算法
- 我關注的那些程式設計師大佬程式設計師
- 那些年,我們一起做過的 Java 課後練習題(71 - 75)Java
- 那些年,我們一起做過的 Java 課後練習題(1 - 5)Java
- [譯] 非同步程式設計:阻塞與非阻塞非同步程式設計
- 純技術團隊創業,那些年我們一起走過的彎路創業
- 那些年,我們解析過的前端異常前端
- 那些年,我們看不懂的那些Kotlin標準函式Kotlin函式
- 小程式開發,那些我們跳過的坑
- 1024程式設計師節即將到來,致敬那些默默工作的程式設計師們程式設計師
- 我們是程式設計師(譯文)程式設計師
- 程式設計我們學到了什麼?程式設計
- 讓我們一起啃演算法----搜尋插入位置演算法
- 那些年,我們處理過的SQL問題SQL
- 說一下那些年,我們遇到的404
- 2019年我們追過的jQuery,它的漏洞你知道嗎?jQuery
- 致畢業生:那些年我們錯過的“BAT”BAT
- 那些年,我們用過的伺服器軟體伺服器
- 聊聊我們那些年用過的表示式引擎元件元件
- 一文徹底搞定(阻塞/非阻塞/同步/非同步)網路IO、併發程式設計模型、非同步程式設計模型的愛恨情仇非同步程式設計模型
- 我們自研的那些Devops工具dev
- 一個老程式設計師的程式設計之路,寫給年輕的程式設計師們程式設計師
- 試圖探尋JavaScript的非同步設計JavaScript非同步
- 那些拼命加班的程式設計師們,後來都怎麼樣了?程式設計師
- 《那些年啊,那些事——一個程式設計師的奮鬥史》——26程式設計師
- 【進階之路】併發程式設計(三)-非阻塞同步機制程式設計
- @Spring Boot程式設計師,我們一起給程式開個後門吧:讓你在保留現場,服務不重啟的情況下,執行我們的除錯程式碼Spring Boot程式設計師除錯
- @Java Web 程式設計師,我們一起給程式開個後門吧:讓你在保留現場,服務不重啟的情況下,執行我們的除錯程式碼JavaWeb程式設計師除錯
- 非同步程式設計:基於事件的非同步程式設計模式(EAP)非同步程式設計事件設計模式
- 那些年讓我們頭疼的CSS3動畫CSSS3動畫
- 那些年,我們見過的 Java 服務端“問題”Java服務端