2015-10-30 40 views
0

我有,我必須電話的異步方法同步,並且就這樣做如下的情況:內存泄漏做異步當在同步

obj.asyncMethod().Wait(myCancelToken) 

如果取消令牌切換即使通過使用聲明激活,該任務內的一次性內容也不會被丟棄。

下面的程序說明了問題:

 using System; 
     using System.Threading; 
     using System.Threading.Tasks; 

     namespace LeakTest { 

      class Program { 
       static void Main(string[] args) { 
        try { 
         var timeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(100)); 
         LongRunningTask().Wait(timeout.Token); 
        } catch (OperationCanceledException error) {     
         // handling timeout is logically okay, but expect nothing to be leaked 
        } 
        Console.WriteLine("Leaked Instances = {0}", DisposableResource.Instances); 
        Console.ReadKey(); 
       } 

       static async Task LongRunningTask() { 
        using (var resource = new DisposableResource()) { 
         await Task.Run(() => Thread.Sleep(1000)); 
        } 
       } 

       public class DisposableResource : IDisposable { 
        public static int Instances = 0; 
        public DisposableResource() { 
         Instances++; 
        } 

        public void Dispose() { 
         Instances--; 
        } 
       } 
      } 
     } 

似乎等待方法只殺死任務線程取消,而不是觸發該線程內的異常,並讓它自然終止。問題是爲什麼?

回答

4

看來wait方法只是殺死在取消任務線程而不是線程中引發異常的

你是不正確的,當你取消的情況是你的唯一的事情上停止等待Wait(myCancelToken)完成,任務仍在後臺運行。

爲了取消後臺任務,您必須將取消標記傳遞到鏈中的所有方法。如果您希望最內層(長時間運行的)早早停止,代碼必須在其代碼中調用token.ThrowIfCancellationRequested()

+0

好的,我看到只有等待被取消。對我來說問題是外部範圍*取決於所有內部示波器在可以處置之前完全處置。如果外部範圍等待隨後取消的任務(即等待恢復但任務仍然自行運行),則這顯然不會發生。 –

5

您已經取消了Wait(timeout.Token)不是一個返回的任務從LongRunningTask回來,如果你想取消一個傳遞令牌Task.Run,還可以使用await Task.Delay代替Thread.Sleep並通過令牌有作爲。

static void Main(string[] args) 
{ 
    try 
    { 
     var timeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(100)); 
     LongRunningTask(timeout.Token).Wait(); 
    } 
    catch (AggregateException error) 
    { 
     // handling timeout is logically okay, but expect nothing to be leaked 
    } 
    Console.WriteLine("Leaked Instances = {0}", DisposableResource.Instances); 
    Console.ReadLine(); 
} 

static async Task LongRunningTask(CancellationToken token) 
{ 

    using (var resource = new DisposableResource()) 
    { 
     await Task.Run(async() => await Task.Delay(1000, token), token); 
    } 
} 

public class DisposableResource : IDisposable 
{ 
    public static int Instances = 0; 
    public DisposableResource() 
    { 
     Instances++; 
    } 

    public void Dispose() 
    { 
     Instances--; 
    } 
} 

注意的使用statment仍將處置資源的一次長時間運行的操作完成。運行該示例:

static void Main(string[] args) 
{ 
    try { 
      var timeout = new CancellationTokenSource(TimeSpan.FromMilliseconds(100)); 
      LongRunningTask().Wait(timeout.Token); 
     } catch (OperationCanceledException error) {     
      // handling timeout is logically okay, but expect nothing to be leaked 
     } 

    Console.WriteLine("Leaked Instances = {0}", DisposableResource.Instances); 

    Console.ReadKey(); 
} 

static async Task LongRunningTask() 
{ 
    using (var resource = new DisposableResource()) 
    { 
     await Task.Run(() => Thread.Sleep(1000)); 
    } 
} 

public class DisposableResource : IDisposable 
{ 
    public static int Instances = 0; 
    public DisposableResource() 
    { 
     Instances++; 
    } 

    public void Dispose() 
    { 
     Instances--; 
     Console.WriteLine("Disposed resource. Leaked Instances = {0}", Instances); 
    } 
} 

輸出
泄露實例= 1個
棄置資源。泄露的實例= 0

+0

在我的情況下,Thread.Sleep實際上是一個沒有取消標記支持的database.BulkInsert調用。對我而言,問題在於外部範圍取決於正在處理的內部範圍內的所有內容,這不會發生,因爲正如您指出的那樣 - 它仍在後臺運行。 –

+0

是的,一旦完成,它將被丟棄。查看我的更新答案的例子。 –

+0

恰好說明了這個概念,謝謝 –