<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # 異步編程 - 從頭開始異步 作者?[Mark Sowul](https://msdn.microsoft.com/zh-cn/magazine/mt149362?author=Mark+Sowul)?| 2015 年 11 月 | 獲取代碼:?[C#](http://download.microsoft.com/download/B/A/E/BAEA7711-903C-4536-92FF-CAC9955EB848/Code_Sowul.Async.1115.zip)[VB](http://download.microsoft.com/download/B/A/E/BAEA7711-903C-4536-92FF-CAC9955EB848/VBCode_Sowul.Async.1115.zip) 借助 Microsoft .NET Framework 的最新版本,通過 async 和 await 關鍵字編寫迅速響應的高性能應用程序不再是難事。可以毫不夸張地說,這些版本改變了 .NET 開發者編寫軟件的方式。曾經需要使用令人費解的嵌套式回叫網的異步代碼現在可以簡單編寫(和理解),幾乎與順序同步代碼一樣。 關于如何創建和使用異步方法的資料足夠多,所以我認定您已對基礎知識很熟悉了。如果您不熟悉,請訪問?[msdn.com/async](http://msdn.com/async)?上的 Visual Studio 文檔頁,這可以幫助您跟上腳步。 大多數異步文檔都會提醒您注意,不能只是將異步方法插入現有代碼;調用方本身需要是異步的。用 Lucian Wischik(Microsoft 語言團隊的一名開發者)的話說,“異步就像是僵尸病毒”。 那么如何在不訴諸于 async void 的前提下,從一開始就在應用程序的結構中構建異步呢? 我將展示幾種重構默認 UI 啟動代碼的方法(既適用于 Windows 窗體,也適用于 Windows Presentation Foundation (WPF)),以及如何將 UI 樣本轉換成面向對象的設計,并添加對 async/await 的支持。在此期間,我還會介紹何時適合或不適合使用“async void”。 在本文中,主要內容是關于 Windows 窗體;WPF 需要進行其他更改,這會造成干擾。在每一步中,我都會先講解對 Windows 窗體應用程序的更改,然后再討論 WPF 版本的相應差異。在本文中,我介紹了所有的基本代碼更改。不過,您還可以下載隨附的聯機代碼,查看兩種環境的完整示例(和中間版本)。 ## 首要步驟 Windows 窗體和 WPF 應用程序的 Visual Studio 模板本身并不真正適合在啟動期間使用異步(或大致自定義啟動流程)。盡管 C# 力求成為面向對象的語言(所有代碼都必須在類中),但默認的啟動代碼促使開發者將邏輯置于靜態方法和 Main 中,或置于主窗體的過度復雜的構造函數中(不適合在 MainForm 構造函數中訪問數據庫。這可是我的親身經驗)。 這種情況一直以來都是一個問題,但現在即使有了異步,也顯然無法讓應用程序進行自我異步初始化。 首先,我在 Visual Studio 中使用 Windows 窗體應用程序模板新建了一個項目。圖 1?展示了 Program.cs 中的默認啟動代碼。 圖 1:默認 Windows 窗體啟動代碼 ~~~ static class Program { ? /// <summary> ? /// The main entry point for the application. ? /// </summary> ? [STAThread] ? static void Main() ? { ??? Application.EnableVisualStyles(); ??? Application.SetCompatibleTextRenderingDefault(false); ??? Application.Run(new Form1()); ? } } ~~~ 對于 WPF,也并不那么容易。默認的 WPF 啟動相當難懂,就算是要找到任何用于自定義的代碼也很困難。您可以將初始化代碼添加到 Application.OnStartup 中,但如何延遲顯示 UI 直到您已加載完所需的數據呢? 首先,我要對 WPF 做的是,將啟動流程公開為我可以編輯的代碼。我將讓 WPF 和 Windows Forms 處于相同的起點,然后本文中的每個步驟對二者來說就都差不多了。 在 Visual Studio 中新建 WPF 應用程序后,我使用圖 2?中的代碼,新建了一個名為“Program”的類。若要替換默認啟動序列,請打開項目屬性,然后將啟動對象從“App”更改為新建的“Program”。 圖 2:等效的 Windows Presentation Foundation 啟動代碼 ~~~ static class Program { ? /// <summary> ? /// The main entry point for the application. ? /// </summary> ? [STAThread] ? static void Main() ? { ??? App app = new App(); ??? // This applies the XAML, e.g. StartupUri, Application.Resources ??? app.InitializeComponent(); ??? // Shows the Window specified by StartupUri ??? app.Run(); ? } } ~~~ 如果您對圖 2?中的 InitializeComponent 調用使用“轉到定義”,那么當您將“App”用作啟動對象時,您會看到編譯器生成等效的 Main 代碼(即我在此處打開“黑盒”的方法)。 ## 實現面向對象的啟動 首先,我會稍微地重構一下默認的啟動代碼,使其走上面向對象的正軌: 我會將邏輯從 Main 中移出,并移至類上。為此,我會將 Program 定義為非靜態類(如我所說,默認設置無法讓您走上正軌),并為其創建一個構造函數。然后,我會將設置代碼移至構造函數中,并添加用于運行我的窗體的 Start 方法。 我已調用了新版 Program1,如圖 3?所示。此主干展現了核心思想:若要運行程序,Main 現在創建對象并在其上調用方法,就像任何典型的面向對象的方案一樣。 圖 3:Program1 - 面向對象的啟動的開端 ~~~ [STAThread] static void Main() { ? Program1 p = new Program1(); ? p.Start(); } private readonly Form1 m_mainForm; private Program1() { ? Application.EnableVisualStyles(); ? Application.SetCompatibleTextRenderingDefault(false); ? m_mainForm = new Form1(); } public void Start() { ? Application.Run(m_mainForm); } ~~~ ## 將應用程序從窗體中分離出來 然而,調用 Application.Run 提取窗體實例(最后在我的 Start 方法中)帶來了一些問題。其中一個就是常規的體系結構問題: 我不喜歡將我的應用程序生存期綁定為顯示相應的窗體。對于許多應用程序來說,這都是可以的,但還是有一些在后臺運行的應用程序不應該在啟動時顯示任何 UI,任務欄或通知區域的圖標可能除外。我見過在啟動時屏幕短暫地閃爍一下,然后消失的情況。我敢肯定的是,它們的啟動代碼遵循類似的流程,在窗體完成加載時,它們便會盡快隱藏起來。無可否認,此時無需解決這一特定問題,但對于異步初始化來說,分離至關重要。 我會重載不提取自變量的 Run,而不使用 Application.Run(m_mainForm)。 無需將它綁定至任何特定的窗體,即可啟動 UI 基礎結構。這種分離意味著我必須自行顯示窗體;同時也意味著,關閉窗體將不再退出應用,所以我也需要將相應的內容明確關聯起來,如圖 4?所示。我也會借此機會為初始化添加我的第一個掛鉤。“Initialize”是一種我為窗體類創建的方法,用于保留初始化它所需的全部邏輯,如從數據庫或網站檢索數據。 圖 4:Program2 - 消息循環和主窗體現在是分離的 ~~~ private Program2() { ? Application.EnableVisualStyles(); ? Application.SetCompatibleTextRenderingDefault(false); ? m_mainForm = new Form1(); ? m_mainForm.FormClosed += m_mainForm_FormClosed; } void m_mainForm_FormClosed(object sender, FormClosedEventArgs e) { ? Application.ExitThread(); } public void Start() { ? m_mainForm.Initialize(); ? m_mainForm.Show(); ? Application.Run(); } ~~~ 在 WPF 版本中,應用的 StartupUri 決定了在調用 Run 時顯示的窗口;您可以在 App.xaml 標記文件中查看它的定義。不出所料,當所有 WPF 窗口均已關閉時,OnLastWindowClose 的應用程序默認 ShutdownMode 設置會關閉應用程序,這就是生存期如何綁定在一起的(請注意,這與 Windows 窗體不同。在 Windows 窗體中,如果主窗口打開了子窗口,而您恰好關閉了第一個窗口,那么應用程序會退出。在 WPF 中,除非您將兩個窗口都關閉,否則應用程序不會退出)。 為了在 WPF 中實現同樣的分離,我會先從 App.xaml 中刪除 StartupUri。相反,我會自行創建窗口,初始化它,并在調用 App.Run 之前顯示它。 ~~~ public void Start() { ? MainWindow mainForm = new MainWindow(); ? mainForm.Initialize(); ? mainForm.Show(); ? m_app.Run(); } ~~~ 在創建應用程序時,我將 app.ShutdownMode 設置為 ShutdownMode.OnExplicitShutdown,以便將應用程序生存期從窗口中分離出來: ~~~ m_app = new App(); m_app.ShutdownMode = ShutdownMode.OnExplicitShutdown; m_app.InitializeComponent(); ~~~ 為了實現顯式關閉,我會為 MainWindow.Closed 附加事件處理程序。 當然,WPF 可以更好地執行分離,因此最好是初始化視圖模型(而不是窗口本身): 我會創建 MainViewModel 類和我自己的 Initialize 方法。同樣,應用關閉請求也應經過視圖模型,所以我會向視圖模型添加“CloseRequested”事件和相應的“RequestClose”方法。生成的 WPF 版本 Program2 如圖 5?所列(Main 保持不變,因此我不會在此處展示它)。 圖 5:Windows Presentation Foundation 版本 Program2 類 ~~~ private readonly App m_app; private Program2() { ? m_app = new App(); ? m_app.ShutdownMode = ShutdownMode.OnExplicitShutdown; ? m_app.InitializeComponent(); } public void Start() { ? MainViewModel viewModel = new MainViewModel(); ? viewModel.CloseRequested += viewModel_CloseRequested; ? viewModel.Initialize(); ? MainWindow mainForm = new MainWindow(); ? mainForm.Closed += (sender, e) => ? { ??? viewModel.RequestClose(); ? }; ? mainForm.DataContext = viewModel; ? mainForm.Show(); ? m_app.Run(); } void viewModel_CloseRequested(object sender, EventArgs e) { ? m_app.Shutdown(); } ~~~ ## 提取宿主環境 現在,我已經將 Application.Run 從我的窗體中分離出來了,我想解決另一體系結構問題。現在,Application 深深嵌入到 Program 類中。就是說,我想把這個宿主環境“抽象出來”。我將從我的 Program 類中刪除各種有關應用程序的 Windows 窗體方法,僅剩下與 Program 本身相關的功能,如圖 6?中的 Program3 所示。最后是要在 Program 類上添加事件,以削弱關閉窗體和關閉應用程序之間的直接關系。請注意 Program3 作為類是如何與應用程序沒有交互的! 圖 6:Program3 - 現在可以在其他位置輕松插入 ~~~ private readonly Form1 m_mainForm; private Program3() { ? m_mainForm = new Form1(); ? m_mainForm.FormClosed += m_mainForm_FormClosed; } public void Start() { ? m_mainForm.Initialize(); ? m_mainForm.Show(); } public event EventHandler<EventArgs> ExitRequested; void m_mainForm_FormClosed(object sender, FormClosedEventArgs e) { ? OnExitRequested(EventArgs.Empty); } protected virtual void OnExitRequested(EventArgs e) { ? if (ExitRequested != null) ??? ExitRequested(this, e); } ~~~ 分離宿主環境有幾大好處。第一個好處就是簡化了測試(您現在可以在有限程度上測試 Program3)。第二個好處就是可以更輕松地在其他位置上重用代碼,可能是嵌入更大的應用程序或“啟動器”屏幕。 分離的 Main 如圖 7?所示(我已將 Application 邏輯重新移入其中)。這種設計更便于集成 WPF 和 Windows 窗體,或逐步將 Windows 窗體替換為 WPF。這不在本文的介紹范圍內,但您可以在隨附的聯機代碼中找到混合應用程序示例。與前面的重構一樣,這些優點并不一定至關重要: 可以說,與“手頭任務”相關的是,將使異步版本更自如地運行(這一點您很快就會發現)。 圖 7:Main - 現可托管任意程序 ~~~ [STAThread] static void Main() { ? Application.EnableVisualStyles(); ? Application.SetCompatibleTextRenderingDefault(false); ? Program3 p = new Program3(); ? p.ExitRequested += p_ExitRequested; ? p.Start(); ? Application.Run(); } static void p_ExitRequested(object sender, EventArgs e) { ? Application.ExitThread(); } ~~~ ## 翹首以待的異步 現在終于有回報了。我可以讓 Start 方法成為異步方法,從而使用 await 并讓初始化邏輯成為異步邏輯。依照慣例,我已將 Start 重命名為 StartAsync,并將 Initialize 重命名為 InitializeAsync。同時,我還已將其返回類型更改為異步任務: ~~~ public async Task StartAsync() { ? await m_mainForm.InitializeAsync(); ? m_mainForm.Show(); } ~~~ 為了使用它,Main 進行了以下更改: ~~~ static void Main() { ? ... ? p.ExitRequested += p_ExitRequested; ? Task programStart = p.StartAsync(); ? Application.Run(); } ~~~ 為了解釋其工作原理,并解決十分細微但卻很重要的問題,我需要深入探究 async/await 的具體情況。 await 的真正含義: 考慮一下我提出的 StartAsync 方法。應當認清(通常情況下)async 方法在遇到 await 關鍵字時返回。正在執行的線程繼續進行,就像返回其他任何方法一樣。在這種情況下,StartAsync 方法遇到“await m_mainForm.InitializeAsync”,并返回至 Main(它會繼續運行),同時調用 Application.Run。這會導致違反常理的結果發生,即 Application.Run 很可能在 m_mainForm.Show 之前執行。盡管按順序來說,它是在 m_mainForm.Show 之后發生。async 和 await 確實會簡化異步編程,但這也絕非易事。 這就是 async 方法返回 Tasks 的原因;Task 完成從直覺上講代表“返回”的 async 方法,即全部代碼均已運行。至于 StartAsync,這意味著它完成了 InitializeAsync 和 m_mainForm.Show。這就是使用 async void 遇到的第一個問題: 如果沒有任務對象,則 async void 方法的調用方將無法獲知它何時完成執行。 如果線程已繼續,且 StartAsync 已返回至其調用方,那么其余的代碼如何以及何時運行呢? 這就是引入 Application.Run 的原因所在。Application.Run 是一個無限循環,負責等待執行任務(主要是處理 UI 事件)。例如,當您在窗口上移動鼠標或單擊按鈕時,Application.Run 消息循環會取消事件排隊,并分派相應的代碼作為回應,然后等待下一事件。不過,這并不嚴格限于 UI: 考慮在 UI 線程上運行函數的 Control.Invoke。Application.Run 也正在處理這些請求。 在這種情況下,只要 InitializeAsync 完成,StartAsync 方法的剩余部分就會發布到消息循環中。當您使用 await 時,Application.Run 會在 UI 線程上執行方法的剩余部分,就像您使用 Control.Invoke 編寫回叫一樣(UI 線程上是否應發生延續受控于 ConfigureAwait。有關詳情,您可以訪問[msdn.com/magazine/jj991977](http://msdn.com/magazine/jj991977),查看 Stephen Cleary 于 2013 年 3 月發布的文章,這是一篇關于異步編程最佳做法的文章)。 這就是將 Application.Run 從 m_mainForm 中分離出來如此重要的原因所在。Application.Run 起主導作用:它必須運行才能處理“await”之后的代碼,在您真正要顯示任意 UI 之前。例如,如果您嘗試將 Application.Run 移出 Main 并移回 StartAsync,那么程序會立即退出: 只要執行到“await InitializeAsync”,Main 就會收回控制,然后就沒有其他要運行的代碼了,這就是 Main 的末尾。 這也解釋了為什么必須要由下而上地開始使用 async。常見的臨時反模式是調用 Task.Wait(而不是 await),因為調用方不是異步方法。但它最可能會立即死鎖。問題在于 UI 線程會被 Wait 調用屏蔽,無法處理延續。沒有延續,任務將無法完成,所以 Wait 調用永遠不會返回任何結果(即死鎖)! Await 和 Application.Run 就是先有雞還是先有蛋的問題: 我之前提到過存在一個十分細微的問題。我介紹過,當您調用 await 時,默認行為是繼續執行 UI 線程,這正是我此時所需要的。不過,當我第一次調用 await 時,相應的基礎結構并未設置,因為相應的代碼尚未運行! SynchronizationContext.Current 是此行為的關鍵: 調用 await 時,基礎結構捕獲 SynchronizationContext.Current 的值,并使用其發布延續;這就是繼續執行 UI 線程的工作方式。當開始運行消息循環時,Windows 窗體或 WPF 設置同步上下文。在 StartAsync 內,此情況尚未發生: 如果您在 StartAsync 開始時檢查 SynchronizationContext.Current,則會看到其為 null。如果無同步上下文,則 await 會改為將延續發布到線程池中,由于不是 UI 線程,因此無效。 WPF 版本會立即掛起,但事實證明,Windows 窗體版本會“意外”生效。默認情況下,當第一個控件創建時(即我構造 m_mainForm 時),Windows 窗體設置同步上下文(此行為受控于 WindowsFormsSynchronizationContext.AutoInstall)。由于在我創建窗體后發生了“await InitializeAsync”,因此我認為沒有問題。不過,如果我將 await 調用置于 creating m_mainForm?*之前*,則會遇到相同的問題。解決辦法是在開始時自行設置同步上下文,如下所示: ~~~ [STAThread] static void Main() { ? Application.EnableVisualStyles(); ? Application.SetCompatibleTextRenderingDefault(false); ? SynchronizationContext.SetSynchronizationContext( ??? new WindowsFormsSynchronizationContext()); ? Program4 p = new Program4(); ? ... as before } ~~~ 對于 WPF,等效調用為: ~~~ SynchronizationContext.SetSynchronizationContext( ? new DispatcherSynchronizationContext()); ~~~ ## 異常處理 馬上就大功告成了! 但在應用程序的根源,我還有另一個問題縈繞于心: 如果 InitializeAsync 出現異常,則程序不會進行處理。programStart 任務對象會包含異常信息,但不會采取任何措施,這樣一來我的應用程序會陷入困境。如果我可以執行“await StartAsync”,則能在 Main 中捕獲異常,但無法使用 await,因為 Main 不是異步的。 這說明了 async void 存在的第二個問題: 無法正確地捕獲 async void 方法引發的異常,因為調用方無權訪問任務對象(那么,您*應該*何時使用 async void 呢? 根據一貫的指導,async void 應主要限于事件處理程序。我之前提到的 2013 年 3 月發布的一篇文章中也介紹了此問題;我建議您閱讀這篇文章,以便充分利用 async/await)。 正常情況下,TaskScheduler.UnobservedException 處理出現隨后不處理的異常的任務。問題在于無法保證運行。在這種情況下,幾乎可以肯定不會運行:在此類任務完成后,任務計劃程序才檢測未觀察到的異常。只有當垃圾回收器運行時才能完成。垃圾回收器僅在它需要滿足請求以獲得更多內存的情況下才運行。 您可能見過這種情況發生: 異常會導致應用程序“坐視不理”,不執行任何操作,所以也就不會請求獲得更多內存,進而導致垃圾回收器也不會運行。結果就是應用掛起。實際上,這就是為什么您不指定同步上下文就會導致 WPF 版本掛起的原因:WPF 窗口構造函數引發異常,因為窗口是在非 UI 線程中創建,然后異常也一直處于未處理狀態。最后一項工作就是要處理 programStart 任務,并添加在出錯時運行的延續。在這種情況下,如果應用程序無法自我初始化,則可以退出。 我無法在 Main 中使用 await,因為它不是異步的,但我可以新建 async 方法,目的僅在于公開(和處理)異步啟動期間引發的所有異常。 它僅包括與 await 有關的 try/catch。因為此方法會處理所有異常,而不引發任何新異常,所以這又是一個 async void 行得通的少數情況: ~~~ private static async void HandleExceptions(Task task) { ? try ? { ??? await task; ? } ? catch (Exception ex) ? { ??? ...log the exception, show an error to the user, etc. ??? Application.Exit(); ? } } ~~~ Main 這樣使用它: ~~~ Task programStart = p.StartAsync(); HandleExceptions(programStart); Application.Run(); ~~~ 當然,像往常一樣,存在一個十分細微的問題(如果 async/await 能夠起到簡化的作用,則您可以想象它曾經有多難)。我之前提到過,*通常情況下*,當 async 方法遇到 await 調用時,它會返回,而此方法的剩余部分會作為延續運行。不過,在某些情況下,任務可以同步完成;如果是這樣的話,則代碼的執行不會中斷(這是一項性能優勢)。然而,如果此時發生這樣的情況,則意味著 HandleExceptions 方法會全部運行,然后返回,而 Application.Run 會緊隨其后: 在這種情況下,如果發生異常,現在 Application.Exit 調用會*先于*?Application.Run 調用發生,將不會產生任何影響。 我要做的是強制 HandleExceptions 作為延續運行: 我需要確保我在執行其他任何操作之前,“直通”Application.Run。這樣一來,如果發生異常,我就知道 Application.Run 已在執行,而 Application.Exit 會正確中斷它。Task.Yield 就做到了這一點: 它強制當前的異步代碼路徑讓步于其調用方,然后作為延續恢復運行。 下面是對 HandleExceptions 的修正: ~~~ private static async void HandleExceptions(Task task) { ? try ? { ??? // Force this to yield to the caller, so Application.Run will be executing ??? await Task.Yield(); ??? await task; ? } ? ...as before ~~~ 在此示例中,當我調用“await Task.Yield”時,HandleExceptions 會返回,且 Application.Run 會執行。然后,HandleExceptions 的剩余部分會作為延續發布到當前的 SynchronizationContext 中,這意味著它會被 Application.Run 提取。 順便說一句,對于理解 async/await 來說,我認為 Task.Yield 是一塊實用的“試金石”。 如果您了解 Task.Yield 的用途,則可能會對 async/await 的工作原理有一個扎實的了解。 ## 回報 現在一切就緒,是時候講點趣味性內容了: 我將展示如何輕松地添加響應式初始屏幕,而不在單獨線程中運行它。撇開趣味性不談,如果您的應用程序無法立即“啟動”,那么擁有初始屏幕就顯得相當重要: 如果用戶在啟動您的應用程序后的好幾秒內都未看到任何動態,就會形成糟糕的用戶體驗。 為初始屏幕啟動單獨的線程是低效的,也是很笨拙的做法。您必須在兩個線程之間正確封送所有調用。因此,提供有關初始屏幕的進度信息是很難的,甚至是關閉它也需要調用 Invoke 或等效方法。此外,當初始屏幕最終關閉時,它通常不會正確地關注主窗體,因為如果初始屏幕和主窗體不在同一線程上,則無法設置兩者之間的所有權。將其與簡易的異步版本進行比較,如圖 8?所示。 圖 8:向 StartAsync 添加初始屏幕 ~~~ public async Task StartAsync() { ? using (SplashScreen splashScreen = new SplashScreen()) ? { ??? // If user closes splash screen, quit; that would also ??? // be a good opportunity to set a cancellation token ??? splashScreen.FormClosed += m_mainForm_FormClosed; ??? splashScreen.Show(); ??? m_mainForm = new Form1(); ??? m_mainForm.FormClosed += m_mainForm_FormClosed; ??? await m_mainForm.InitializeAsync(); ??? // This ensures the activation works so when the ??? // splash screen goes away, the main form is activated ??? splashScreen.Owner = m_mainForm; ??? m_mainForm.Show(); ??? splashScreen.FormClosed -= m_mainForm_FormClosed; ??? splashScreen.Close(); ? } } ~~~ ## 總結 我已經展示了如何將面向對象的設計應用于您的應用程序的啟動代碼(無論是 Windows 窗體還是 WPF),它可以輕松地支持異步初始化。我還展示了如何解決異步啟動流程中的一些十分細微的問題。您若要真正進行異步初始化,我恐怕您只能靠您自己了,但您可以在?[msdn.com/async](http://msdn.com/async)?上找到一些指南。 學會使用 async 和 await 只是個開始。現在,程序更面向對象,其他功能的實施變得更加直截了當。我可以通過對 Program 類調用相應的方法,處理命令行自變量。我可以讓用戶在主窗口顯示前進行登錄。我可以在通知區域啟動應用,而不在啟動時顯示任何窗口。通常,面向對象的設計可便于您擴展和重用代碼中的功能。 * * * 事實上,Mark Sowul*可能是在用 C# 編寫軟件模擬(人們如此推測)。Sowul 從一開始就是一名敬業的 .NET 開發者,通過紐約咨詢公司 SolSoft Solutions 分享了他在 .NET 和 Microsoft SQL Server 方面有關體系結構和性能的豐富專業知識。通過?[mark@solsoftsolutions.com](mailto:mark@solsoftsolutions.com)?與他取得聯系,訪問[eepurl.com/_K7YD](http://eepurl.com/_K7YD)?訂閱他的臨時電子郵件,了解他對軟件的獨特見解。*
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看