2016-05-02 26 views
7

tôi có nhu cầu theo dõi một nhiệm vụ và có khả năng xếp hàng nhiệm vụ khác sau khi một số chậm trễ vì vậy cách Tôi đang nghĩ đến việc làm nó trông giống như sau:nhiệm vụ Chaining với sự chậm trễ

private Task lastTask; 

public void DoSomeTask() 
{ 
    if (lastTask == null) 
    { 
     lastTask = Task.FromResult(false); 
    } 
    lastTask = lastTask.ContinueWith(t => 
    { 
     // do some task 
    }).ContinueWith(t => Task.Delay(250).Wait()); 
} 

Câu hỏi của tôi là, nếu tôi làm một cái gì đó như thế này, việc tạo ra các chuỗi nhiệm vụ dài có thể là các nhiệm vụ cũ sẽ được xử lý hoặc chúng sẽ kết thúc mãi mãi bởi vì ContinueWith nhận nhiệm vụ cuối cùng làm tham số (vì vậy nó là một đóng). Nếu vậy, làm thế nào tôi có thể chuỗi nhiệm vụ trong khi tránh vấn đề này?

Có cách nào tốt hơn để thực hiện việc này không?

+0

Bạn có thể giả định rằng 'DoSomeTask()' sẽ không được gọi song song? –

+0

@YacoubMassad Nó sẽ không ảnh hưởng đến các câu hỏi cụ thể mà anh ta hỏi về ngay cả khi anh ta. – Servy

+0

Bất kỳ cơ hội nào bạn có thể sử dụng 'async'/'await'? Mã sẽ được cách đơn giản và dễ dàng hơn để lý do. –

Trả lời

1
Task.Delay(250).Wait() 

Bạn biết bạn đang làm điều gì sai khi bạn sử dụng Wait trong mã bạn đang cố gắng để làm cho đồng bộ. Đó là một chủ đề lãng phí không làm gì cả.

Sau đây sẽ là tốt hơn nhiều:

lastTask = lastTask.ContinueWith(t => 
{ 
    // do some task 
}).ContinueWith(t => Task.Delay(250)).Unwrap(); 

ContinueWith trả về một Task<Task>, và cuộc gọi Unwrap biến đó vào một Task đó sẽ hoàn thành khi nhiệm vụ nội làm.

Bây giờ, để trả lời câu hỏi của bạn, chúng ta hãy nhìn vào những gì trình biên dịch tạo:

public void DoSomeTask() 
{ 
    if (this.lastTask == null) 
     this.lastTask = (Task) Task.FromResult<bool>(false); 
    // ISSUE: method pointer 
    // ISSUE: method pointer 
    this.lastTask = this.lastTask 
    .ContinueWith(
     Program.<>c.<>9__2_0 
     ?? (Program.<>c.<>9__2_0 = new Action<Task>((object) Program.<>c.<>9, __methodptr(<DoSomeTask>b__2_0)))) 
    .ContinueWith<Task>(
     Program.<>c.<>9__2_1 
     ?? (Program.<>c.<>9__2_1 = new Func<Task, Task>((object) Program.<>c.<>9, __methodptr(<DoSomeTask>b__2_1)))) 
    .Unwrap(); 
} 

[CompilerGenerated] 
[Serializable] 
private sealed class <>c 
{ 
    public static readonly Program.<>c <>9; 
    public static Action<Task> <>9__2_0; 
    public static Func<Task, Task> <>9__2_1; 

    static <>c() 
    { 
     Program.<>c.<>9 = new Program.<>c(); 
    } 

    public <>c() 
    { 
     base.\u002Ector(); 
    } 

    internal void <DoSomeTask>b__2_0(Task t) 
    { 
    } 

    internal Task <DoSomeTask>b__2_1(Task t) 
    { 
     return Task.Delay(250); 
    } 
} 

này được dịch ngược với dotPeek trong "cho tôi tất cả can đảm" chế độ.

Nhìn vào phần này:

.ContinueWith<Task>(
    Program.<>c.<>9__2_1 
    ?? (Program.<>c.<>9__2_1 = new Func<Task, Task>((object) Program.<>c.<>9, __methodptr(<DoSomeTask>b__2_1)))) 

Chức năng ContinueWith được đưa ra một đại biểu singleton. Vì vậy, không có đóng cửa trên bất kỳ biến ở đó.

Bây giờ, có chức năng này:

internal Task <DoSomeTask>b__2_1(Task t) 
{ 
    return Task.Delay(250); 
} 

Các t đây là một tham chiếu đến các nhiệm vụ trước. Chú ý một cái gì đó? Nó không bao giờ được sử dụng. JIT sẽ đánh dấu địa phương này là không thể truy cập được, và GC sẽ có thể làm sạch nó. Khi tối ưu hóa được kích hoạt, JIT sẽ đánh dấu tích cực những người dân địa phương đủ điều kiện để thu thập, thậm chí đến mức một phương pháp thể hiện có thể thực hiện trong khi trường hợp đang được GC thu thập, nếu phương pháp thể hiện không tham chiếu this trong mã còn lại để thực thi.

Bây giờ, một điều cuối cùng, có trường m_parent trong lớp Task, điều này không tốt cho kịch bản của bạn. Nhưng miễn là bạn không sử dụng TaskCreationOptions.AttachedToParent bạn sẽ ổn thôi. Bạn luôn có thể thêm cờ DenyChildAttach để tăng thêm sự an toàn và tự tài liệu.

Đây là function which deals with that:

internal static Task InternalCurrentIfAttached(TaskCreationOptions creationOptions) 
{ 
    return (creationOptions & TaskCreationOptions.AttachedToParent) != 0 ? InternalCurrent : null; 
} 

Vì vậy, bạn nên an toàn ở đây. Nếu bạn muốn được chắc chắn, hãy chạy trình thu thập bộ nhớ trên một chuỗi dài và tự mình xem.

2

nếu tôi làm điều gì đó như thế này, tạo ra khả năng chuỗi dài của nhiệm vụ là nhiệm vụ lớn tuổi sẽ được xử lý

Nhiệm vụ không yêu cầu xử lý rõ ràng, vì chúng không chứa các nguồn lực không được quản lý.

họ sẽ kết thúc gắn bó xung quanh mãi mãi vì ContinueWith mất nhiệm vụ cuối cùng như một tham số (vì vậy đó là một đóng)

Đó là không đóng cửa. Việc đóng là một phương thức ẩn danh sử dụng biến số từ bên ngoài phạm vi của phương thức ẩn danh đó trong cơ thể của nó. Bạn không làm điều đó, vì vậy bạn không đóng cửa nó. Tuy nhiên, mỗi Task có một trường mà nó theo dõi phụ huynh của nó, do đó đối tượng Task được quản lý sẽ vẫn có thể truy cập được nếu bạn đang sử dụng mẫu này.

+1

Bố mẹ phải là rỗng trong kịch bản của mình, miễn là anh ta tránh 'TaskCreationOptions.AttachedToParent'. –

2

Hãy xem qua số source code of the ContinuationTaskFromTask class. Mã có mã sau:

internal override void InnerInvoke() 
{ 
    // Get and null out the antecedent. This is crucial to avoid a memory 
    // leak with long chains of continuations. 
    var antecedent = m_antecedent; 
    Contract.Assert(antecedent != null, 
     "No antecedent was set for the ContinuationTaskFromTask."); 
    m_antecedent = null; 

m_antecedent là trường có tham chiếu đến yêu cầu tiền định trước. Các nhà phát triển ở đây rõ ràng đặt nó là null (sau khi nó không còn cần thiết) để đảm bảo rằng không có rò rỉ bộ nhớ với chuỗi dài của sự tiếp tục, mà tôi đoán là mối quan tâm của bạn.

Các vấn đề liên quan