主頁 > .NET開發 > 基于任務的異步編程

基于任務的異步編程

2020-09-17 17:17:54 .NET開發

博客遷移

記錄《Effective C#》學習程序,

任務運行的幾種方法

//1.new方式實體化一個Task,需要通過Start方法啟動
         Task task = new Task(() =>
         {               
             Console.WriteLine($"task1的執行緒ID為{Thread.CurrentThread.ManagedThreadId}");
         });
         task.Start();

         //2.Task.Factory.StartNew(Action action)創建和啟動一個Task
         Task task2 = Task.Factory.StartNew(() =>
           {        
               Console.WriteLine($"task2的執行緒ID為{Thread.CurrentThread.ManagedThreadId}");
           });

         //3.Task.Run(Action action)將任務放在執行緒池佇列,回傳并啟動一個Task
         Task task3 = Task.Run(() =>
           {                  
               Console.WriteLine($"task3的執行緒ID為{ Thread.CurrentThread.ManagedThreadId}");
           });
View Code

使用異步方法執行異步作業

對于呼叫異步方法的主調方法來說,只要異步方法已經回傳,這里回傳的是Task物件,它就可以繼續往下執行,

public static async void MainMethod()
{
    var task = TaskMethod(); //呼叫的開始,異步方法就在跑了

    TaskStatus taskStatus = task.Status; 
    //任務的狀態
    //Created = 0,
    //WaitingForActivation = 1,
    //WaitingToRun = 2,
    //Running = 3,
    //WaitingForChildrenToComplete = 4,
    //RanToCompletion = 5,
    //Canceled = 6,
    //Faulted = 7            

    var a = "";
    var b = "";
    var c = "";
    var d = "";

    var result = await task;

    var sum = result + 2000;
}

public static async Task<int> TaskMethod()
{
    var task = GetTask();
    return await task;
}
View Code

主呼叫方法執行到await的時候,Task如果已經完成,則會回傳一個已完成狀態的Task物件,并且繼續執行await的下一條陳述句,就像同步一樣,

主呼叫方法執行到await的時候,Task如果還未完成,編譯器把await后面的陳述句生成delegate,寫入相應的狀態資訊,直到任務完成,會有一個SynchronizationContext類恢復delegate運行的情境到await之前的樣子(控制臺是沒有SynchronizationContext的),

一定要等候任務的執行結果,否則有例外也不會拋出來,

Task.Wait()、Task.Result等候Task執行完畢,才往下跑,但是會讓當前執行緒阻塞,

不要寫回傳值型別為void的異步方法

主調方法呼叫回傳回傳值為void的異步方法,如果異步方法執行報錯,主調方法無法catch到它的例外,只能通過App.Domain.UnhandleException事件或其他非常規手段來處理例外,

通過AppDomain.UnhandleExceptioin事件處理例外并不能讓程式從例外中恢復,

無法等待回傳值為void的異步方法的執行結果,就無法輕易判斷它什么時候執行完,

 private async void Button1_Click(object sender, EventArgs e)
   {
       try
       {
           Test();
       }
       catch(Exception ex)
       {
           //斷點進不到catch
       }            
   }  

//回傳值為void的異步方法
   static async void Test()
   {
       var task = GetTask();
       var result = await task;            
   }

   /// <summary>
   /// 應用程式的主入口點,
   /// </summary>
   [STAThread]
   static void Main()
   {
       Application.EnableVisualStyles();
       Application.SetCompatibleTextRenderingDefault(false);

       AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

       Application.Run(new Form1());

   }
   private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
   {
       //斷點可以進來
       throw new NotImplementedException();
   }
View Code

如果要寫回傳值為void的異步方法,一定要做好例外處理

第一種:簡單的記錄例外,不會妨礙程式繼續往下執行

static async void Test1()
{    
    try
    {
        var task = GetTask();
        await task;
    }
    catch (Exception ex)
    {
        Log(ex.ToString()); //偽代碼            
    }
}
View Code

第二種:借助例外過濾器

static async void Test1()
{                        
    try
    {
        var task = GetTask();
        await task;
    }
    catch(Exception ex)when(LogMessage(ex)) 
    {
        //1:如果LogMessage回傳true,可以catch到例外,程式還能往下執行,
        //如果catch里面又拋出例外,另說,
        //2:第二如果LogMessage回傳false,catch不到例外,會把例外重新拋出,
        //能在AppDomain.CurrentDomain.UnhandledException捕捉,整個程式會                 //停掉
    }
}

static bool LogMessage(Exception ex)
{
    Log(ex.ToString()); //偽代碼
    return false;
}
View Code

第三種:把所執行的異步作業視為Task,處理例外的邏輯分別表示通用的Action<Exception>Func<Exception,bool>

static async void Test1(this Task task,Action<Exception> one rrors)
{                        
    try
    {                             
        await task;
    }
    catch(Exception ex)
    {
        one rrors(ex);
    }
}
static async void Test2(this Task task, Func<Exception,bool> one rrors)
{
    try
    {
        await task;
    }
    catch (Exception ex)when(onErrors(ex))
    {
        one rrors(ex);
    }
}
View Code
static async void Test1(this Task task,Action<Exception> one rrors)
{                        
    try
    {                             
        await task;
    }
    catch(Exception ex)
    {
        one rrors(ex);
    }
}
static async void Test2(this Task task, Func<Exception,bool> one rrors)
{
    try
    {
        await task;
    }
    catch (Exception ex)when(onErrors(ex))
    {
        one rrors(ex);
    }
}
View Code

如果希望有些例外能從中恢復

static async void Test2<TException>(this Task task, Action<TException> recovery,Func<Exception,bool> one rror) 
            where TException : Exception
        {
            try
            {
                await task;
            }
            catch (Exception ex)when(onError(ex))  
            {
                
            }
            catch(TException ex2) //如果onError回傳false,就有可能catch到TException,并從中恢復
            {
                recovery(ex2);
            }
        }
View Code

不要同步方法與異步方法組合使用

原因一:同步調異步,無非就是Task.Wait()或者Task.Result實作,但這兩個方法拋出的例外是非具體的,而是AggregateException型別例外,真正的例外在這個例外里面,

public static int GetSum()
{
    try
    {
        var task1 = GetTask1();
        var task2 = GetTask2();
        var result1 = task1.Result;
        var result2 = task2.Result;
        return result1 + result2;
    }
    catch(AggregateException e)when(e.InnerExceptions.FirstOrDefault().GetType()==typeof(KeyNotFoundException))
    {
        return 0;
    }
}
View Code

原因二:代碼如下,可能發生死鎖,

舉例:

GUI及Asp.Net情境下的SynchronizationContext只包含一條執行緒,

Task.Wait()會讓執行緒阻塞,而await下面的陳述句又需要這條執行緒才能跑,

private async void Button1_Click(object sender, EventArgs e)
{
    var task = Test();

    string a = "";
    string b = "";
    string c = "";
    string d = "";

    _ = task.Result;

    Console.WriteLine("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
                      "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
                      "aaaaaaaaaaaaaaaaaaaaaa");
}

static async Task<bool> Test()
{
    await Task.Delay(2000);
    string a = "";
    string b = "";
    string c = "";
    string d = "";
    return true;
}
View Code

上面例子補充:與Thread.Sleep相比,Task.Delay是一種異步的延時機制,允許執行緒去做其他事,

第二種情況:異步里啟動另一個異步任務,并在另一個異步任務里執行計算量較大的同步操作,

原因一:本來就有執行緒執行這項異步操作,沒必要需要開辟更多的執行緒執行,

原因二:異步方法開辟新的執行緒執行計算量較大的同步操作,誤導開發呼叫者,

private async void Button1_Click(object sender, EventArgs e)
    {
        MessageBox.Show(Thread.CurrentThread.ManagedThreadId.ToString());
    //除錯得到 當前執行緒ID:1
        await GetTaskAsync();
    }

    public double ComputeValue()
    {
        MessageBox.Show(Thread.CurrentThread.ManagedThreadId.ToString());
        //除錯得到 當前執行緒ID:4
        double finalAnswer = 0;
        for (int i = 0; i < 100000000; i++)
        {
            finalAnswer += i;
        }
        return finalAnswer;
    }     

    public async Task<double> GetTaskAsync()
    {
        var task = new Task<double>(()=> {                                MessageBox.Show(Thread.CurrentThread.ManagedThreadId.ToString());
//除錯得到 當前執行緒ID:3
            Task.Run(() => ComputeValue());
            return 2;
        });

        task.Start();
        var result = await task;
        return result;
    }
View Code

異步任務嵌套異步任務是可以的,只是應該是將自己無法完成或者不便完成的任務交給另外的異步去做,而不是隨意開辟新的執行緒,把本來就可以自己執行的作業轉交出去,

使用異步方法,要考慮執行緒分配和背景關系切換的開銷

可以異步,但不要隨便用,

原因一:執行緒成本,當前執行緒就能做好的作業轉交給另一個執行緒做、前面執行緒的確減輕負擔,但后面執行緒也增加負擔了,所以在當前執行緒是稀缺且重要的資源,例如GUI應用程式的UI執行緒,才應該把計算量較大的作業轉交給其他異步去做,

原因二:背景關系切換成本,await任務之后,可以正常往下執行,是因為SynchronizationContext記住了await之前的所有狀態,等任務執行完后,切換到原來的SynchronizationContext

有些異步沒有必要開辟新執行緒,例如檔案異步I/O、Web請求,檔案異步可以通過埠實作,Web請求可以通過網路中斷實作,

ConfigureAwait(false)方法使用

如果await陳述句之后的代碼與背景關系無關,可以通過呼叫Task物件的ConfigureAwait(false)告訴系統不必切回到原理捕獲的背景關系中運行,默認是true,

使用ConfigureAwait(false)好處是提高性能,避免死鎖,

private async void Button1_Click(object sender, EventArgs e)
      {       
          await     GetTaskAsync().ConfigureAwait(continueOnCapturedContext:false); //一般不在應用程式級別代碼使用false,這里只是舉例子,

          //必須在特定的背景關系中執行,如果上面設為false
          //拋例外 System.InvalidOperationException:“執行緒間操作無效:
          //從不是創建控制元件“button2”的執行緒訪問它,”
          //button2.Text = "dddd"; 
      }

如果是在某條await陳述句處呼叫ConfigureAwait(false),而且這里await的任務是異步執行的,系統會把下面的代碼安排到默認的背景關系中去,一旦這樣做,很難切回最初捕獲的背景關系,

private async void Button1_Click(object sender, EventArgs e)
      {       
          await GetTaskAsync().ConfigureAwait(continueOnCapturedContext:false); 

          await GetTaskAsync();
          await GetTaskAsync();

          string aa = ""; //在默認的背景關系中執行,回不到第一個await之前捕獲的背景關系了,
      }

但是可以通過調整代碼結構,把背景關系無關的代碼移到新方法,

private async void OnCommand(object sender,RoutedEventArgs e){
    var viewModel = DataContext as SampleViewModel;
    try{
        Config config = await ReadConfigAsync(viewModel); 
        await viewModel.Update(config); //更新UI控制元件,需要在特定的背景關系里
    }
    catch(Exception ex)when(logMessage(viewModel,ex)){ 
        
    }    
}

//不需要在特定的背景關系中執行
private async Task<Config> ReadConfigAsync(SampleViewModel viewModel){
    var userInput = viewModel.webSite;
    var result = await DownloadAsync(userInput).ConfigureAwait(false);
    var items = XELement.Parse(result);
    var userConfig = from node in items.Descendants()
        where node.Name == "Config"
        select node.Value;
    var configUrl = userConfig.SingleOrDefault();
    if(configUrl != null){
        result = await DownloadAsync(configUrl).ConfigureAwait(false); //雖然前面有了ConfigureAwait(false),但依然要寫上
        config = await ParseConfig(result)
            .ConfigureAwait(false);
    }
    else{
        config = new Config();
    }
    return config;
}
View Code

如果撰寫的是應用程式級代碼,不要使用ConfigureAwait(false),避免程式崩潰,詳細閱讀ConfigureAwait常見問題解答

Task物件

Task物件只是執行異步的一個載體,它有幾個重要的方法:Task.WhenAll、Task.WhenAny,

private async void Button1_Click(object sender, EventArgs e)
        {
            var tasks = new List<Task<int>>();
            tasks.Add(GetTask());
            tasks.Add(GetTask());
            tasks.Add(GetTask());
            tasks.Add(GetTask());
            tasks.Add(GetTask());

            //WhenAll 會根據現有的一批任務創建一個新任務
            var results = await Task.WhenAll(tasks);

            //Task.whenAny回傳的是最先執行完畢的那項任務
            var result = await (await Task.WhenAny(tasks));
        }

        private async Task<int> GetTask()
        {
            var task = new Task<int>(() =>
            {
                return 5;
            });
            task.Start();
            
            return await task;
        }
View Code

如果有多項任務,而且要求必須對已經執行的每項任務的結果做一些處理,這些任務不會互相依賴,在考慮性能的情況下,當然想哪些先完成,哪些結果就先拿來處理,首先想到是用WhenAny方法,但是每一次WhenAny就創建一項新任務,效率不太好,這時可以考慮使用TaskCompletionSource,這是一個可以容納異步任務執行結果的地方,

public static Task<T>[] OrderByCompletion<T>(this IEnumerable<Task<T>> tasks)
      {
          var sourceTasks = tasks.ToList();
          var completionSources = new TaskCompletionSource<T>[sourceTasks.Count];
          var outputTasks = new Task<T>[completionSources.Length];
          for(int i = 0; i < completionSources.Length; i++)
          {
              completionSources[i] = new TaskCompletionSource<T>();
              outputTasks[i] = completionSources[i].Task;
          }

          int nextTaskIndex = -1;
    //每項任務執行完后,然后執行的方法,
          Action<Task<T>> continuation = completed =>
          {
              //Interlocked.Increment確保執行緒安全
              var bucket = completionSources[Interlocked.Increment(ref nextTaskIndex)];
              bucket.TrySetResult(completed.Result);
          };

          foreach(var inputTask in sourceTasks)
          {
              //借用了委托,當任務完成后,在委托方法里處理任務結果
              inputTask.ContinueWith(continuation, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously,
                  TaskScheduler.Default);
          }

          return outputTasks;
      }

      // 摘要:
      //     創建根據 continuationOptions 中指定的條件加以執行的延續任務,
      //
      // 引數:
      //   continuationAction:
      //     根據在 continuationOptions 中指定的條件運行的操作, 在運行時,委托將作為一個自變數傳遞給完成的任務,       
      //   continuationOptions:
      //     用于設定計劃延續任務的時間以及延續任務的作業方式的選項,       
      public Task ContinueWith(Action<Task<TResult>> continuationAction, CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, TaskScheduler scheduler);

  [Flags]
  public enum TaskContinuationOptions
  {
     ......
     ......
      //
      // 摘要:
      //     指定應同步執行延續任務, 指定此選項后,延續任務在導致前面的任務轉換為其最終狀態的相同執行緒上運行, 如果在創建延續任務時已經完成前面的任務,則延續任務將在創建此延續任務的執行緒上運行,
      //     如果前面任務的 System.Threading.CancellationTokenSource 已在一個 finally(在 Visual Basic
      //     中為 Finally)塊中釋放,則使用此選項的延續任務將在該 finally 塊中運行, 只應同步執行運行時間非常短的延續任務, 由于任務以同步方式執行,因此無需呼叫諸如
      //     System.Threading.Tasks.Task.Wait 的方法來確保呼叫執行緒等待任務完成,
      ExecuteSynchronously = 524288
  }
View Code

考慮任務支持取消功能

可以通過CancellationToke這個struct型別實作任務的取消功能,如果呼叫者請求取消,則ThrowIfCancellationRequested()方法會拋出System.OperationCanceledException例外,

public Task RunPayroll() => RunPayroll(new CancellationToken(), null);
public Task RunPayroll(CancellationToken cancellationToken) => RunPayroll(cancellationToken, null);
public Task RunPayroll(IProgress<int, string> progress) => RunPayroll(new CancellationToken(), null);

      public async Task RunPayroll(CancellationToken cancellationToken,IProgress<int,string> progress)
      {
          progress?.Report(0, "第一步");
          var result0 = await RunTask0();
          cancellationToken.ThrowIfCancellationRequested();

          progress?.Report(1, "第二步");
          var result1 = await RunTask1();
          cancellationToken.ThrowIfCancellationRequested();

          progress?.Report(1, "第三步");
          var result2 = await RunTask2();
          cancellationToken.ThrowIfCancellationRequested();

          progress?.Report(1, "第四步");
          var result3 = await RunTask3();
          cancellationToken.ThrowIfCancellationRequested();
      }

/// <summary>
      /// 監控進度
      /// </summary>
      /// <typeparam name="T"></typeparam>
      /// <typeparam name="T1"></typeparam>
      public interface IProgress<T, T1>
      {
           void Report(T t, T1 t1);
      }
View Code

呼叫方可以通過CancellationTokenSource物件請求取消

private async void Button1_Click(object sender, EventArgs e)
{
    var cts = new CancellationTokenSource();

    try
    {
        var task = RunPayroll(cts.Token);
        cts.Cancel(); //取消
        await task;
    }
    catch(OperationCanceledException ex)
    {

    }                
}
View Code

如果異步任務方法的回傳值是void,呼叫方無法遵循正常途徑處理例外,只能通過專門的處理程式處理例外,因此,建議回傳值為void的異步方法不支持取消功能,

快取異步方法的回傳值

如果程式因為頻繁分配Task物件而使得效率低下,可以考慮使用ValueTask優化,ValueTask提供了一個接受Task引數的建構式,ValueTask是Struct型別,

public ValueTask<IEnumerable<int>> GetData(int a,int b)
{
    if (a < b)
    {
        return new ValueTask<IEnumerable<int>>(cacheData); //從快取中取
    }
    else
    {
        async Task<IEnumerable< int >> load() //內嵌異步方法
        {
            var result = await RunTask();
            return result;
        }
        return new ValueTask<IEnumerable<int>>(load()); //接受Task引數的建構式
    }
}
View Code

千萬確認性能瓶頸是因為記憶體分配的開銷導致,再考慮把Task換成ValueTask,如果需要實時獲取資料就沒必要使用ValueTask,

參考書籍:《Effective C#》進階篇,針對C# 7.0更新

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/67177.html

標籤:C#

上一篇:VS 2019中修改C#語言版本

下一篇:使用HtmlAgilityPack開發爬蟲篩選HTML時,關于xpath的坑

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more