2012-02-13 30 views
12

phương pháp ban đầu của tôi trông giống như:Đây có phải là ok để lấy được từ TPL Task để trả lại chi tiết hơn từ phương pháp?

string DoSomeWork(); 

Phương pháp DoSomeWork bắt đầu một số công việc về chủ đề khác và trả về thi công ID (chỉ chuỗi ngẫu nhiên). Sau đó, tôi có thể truy vấn kết quả bằng ID thực thi đã cho. Điểm chính là thực hiện ID thực thi trước khi hoàn thành công việc.

Bây giờ tôi muốn thay đổi chữ ký để trả lại Tác vụ, vì vậy, người dùng có thể đợi nếu muốn.

Task DoSomeWork(); 

Đồng thời tôi vẫn cần phải trả lại ID thực thi (ví dụ như cho mục đích truy tìm) và tôi thấy một số tùy chọn. Đầu tiên nếu tham số out, thứ hai là trả về tuple với cả ID thực thi và tác vụ (trong C# thì đây không phải là lựa chọn tốt nhất) và thứ ba mà tôi thực sự muốn hỏi.

gì nếu tôi sẽ tạo ra lớp đó sẽ xuất phát từ Task lớp:

public class ExtendedTask : Task 
{ 
    public string ExecutionID {get; set;} 
} 

Liệu điều này có vẻ ok? Hoặc tốt hơn là quyết định các lựa chọn khác?

P.S. Trong BCL có một số bắt nguồn từ các lớp Task.

CẬP NHẬT, có vẻ như tôi không thể xác định rõ ràng điều này. Nhưng tôi cần truy cập vào ExecutionID trước khi hoàn thành công việc và vì vậy tôi không thể sử dụng Task.Result.

Trả lời

12

Tôi sẽ không cá nhân mở rộngTask<T>, tôi muốn soạn thay vì đó là. Bằng cách đó bạn không cần phải lo lắng về bất kỳ API nào chỉ trả về Task<T> - bạn chỉ có thể thực hiện nhiệm vụ. Bạn có thể có thuộc tính mà hiển thị tác vụ cơ bản và cho mục đích không đồng bộ C# 5, bạn có thể triển khai mẫu awaiter theo kiểu của riêng bạn - nhưng cảm giác với tôi như tạo loại có nguồn gốc của riêng bạn là có khả năng tốt hơn. Nó hầu như là một cảm giác ruột.

Một tùy chọn khác là làm việc theo cách khác vòng: lưu trữ trạng thái bổ sung của bạn trong thuộc tính Task.AsyncState; đó là những gì nó có cho, sau khi tất cả.Bằng cách đó bạn có thể dễ dàng vượt qua các nhiệm vụ xung quanh mà không làm mất bối cảnh thực hiện nó là một phần logic.

+0

Cảm ơn 'Task.AsyncState' Tôi không biết về nó. Tôi chỉ quan tâm là tại sao nó phản đối? Ai đó có thể ghi đè lên nó. –

+1

@MikeChaliy Nó được thiết kế để triển khai IAsyncResult và chỉ đọc. Không ai có thể ghi đè lên bạn khi bạn xây dựng nhiệm vụ của mình. Bạn cần phải xây dựng Nhiệm vụ bằng cách sử dụng một trong các hàm tạo có 'Hành động ' thay vì 'Hành động' để có được tập hợp này ... –

+0

@ReedCopsey up, vâng, bạn nói đúng. Vâng, trông đẹp hơn sau đó. –

11

Tôi khuyên bạn nên sử dụng Task<T> để thay thế, vì nó cho phép bạn "nhúng" thông tin khác vào Kết quả của tác vụ.

Ví dụ, trong trường hợp của bạn, nó có thể làm cho tinh thần để có một cái gì đó như:

class ExecutionResult 
{ 
    public int ExecutionID { get; set; } 
    public string Result { get; set; } 
    // ... 
} 


public Task<ExecutionResult> DoSomeWork() 
{ 
    return Task.Factory.StartNew(() => 
    { 
      // Replace with real work, etc... 
      return new ExecutionResult { ExecutionID = 0, Result = "Foo" }; 
    }); 
} 

Chỉnh sửa để đáp ứng với nhận xét:

Nếu bạn cần dữ liệu "trước khi" Task hoàn thành và đang cố gắng truy cập mục đích này cho các mục đích khác, tôi khuyên bạn nên tạo lớp có chứa Tác vụ và dữ liệu khác và trả lại lớp đó, tức là:

class ExecutionResult 
{ 
    public int ExecutionID { get; private set; } 
    public Task<string> Result { get; private set; } 
    // ... Add constructor, etc... 
} 


public ExecutionResult DoSomeWork() 
{ 
    var task = Task.Factory.StartNew(() => 
    { 
      // Replace with real work, etc... 
      return "Foo"; 
    }); 

    return new ExecutionResult(1, task); // Make the result from the int + Task<string> 
} 

Điều này sẽ vẫn cho phép bạn truy cập thông tin về quy trình của bạn và Task/Task<T>.

+0

'Tác vụ.Result' sẽ khả dụng sau khi hoàn thành công việc. Tôi cần giá trị này trước đây. –

+1

@MikeChaliy Đã chỉnh sửa để cho biết cách tôi tiếp cận nó trong trường hợp này ... –

+0

Cảm ơn câu trả lời của bạn. –

1

Nếu bạn làm quyết định kế thừa từ Task hoặc Task<TResult>, bạn có thể gặp sự thất vọng rằng Action<Object> hoặc Func<Object,TResult> đại biểu cung cấp các công việc thực tế cho nhiệm vụ phải được xác định vào thời điểm đó Task có nguồn gốc từ đối tượng được xây dựng và không thể thay đổi sau này. Điều này đúng ngay cả khi các hàm tạo của lớp cơ sở không Start() tác vụ mới được tạo ra, và trên thực tế nó có thể không được bắt đầu cho đến sau này, nếu có.

Điều này gây khó khăn khi sử dụng lớp học Task trong trường hợp các phiên bản phải được tạo trước khi có đầy đủ chi tiết về công việc cuối cùng của nó.

Một ví dụ có thể là một mạng vô định hình của nổi tiếng Task<TResult> nút làm việc trên một mục tiêu chung như vậy mà họ truy cập Result tính của nhau trong một cách ad-hoc. Cách đơn giản nhất để đảm bảo rằng bạn có thể Wait() trên bất kỳ nút tùy ý nào trong mạng là xây dựng trước tất cả các nút trước khi bắt đầu bất kỳ nút nào trong số chúng. Điều này tránh được vấn đề cố gắng phân tích phụ thuộc đồ thị công việc và cho phép các yếu tố thời gian chạy xác định khi nào, nếu và theo thứ tự nào, giá trị Result được yêu cầu.

Vấn đề ở đây là, đối với một số nút, bạn có thể không thể cung cấp chức năng xác định công việc tại thời điểm xây dựng. Nếu việc tạo hàm lambda cần thiết yêu cầu đóng trên Result giá trị từ các tác vụ khác trong mạng, thì Task<TResult> cung cấp Result chúng tôi muốn có thể chưa được tạo. Và ngay cả khi nó xảy ra đã được xây dựng trước đó trong giai đoạn tiền xây dựng, bạn không thể gọi Start() trên nó vì nó có thể kết hợp phụ thuộc vào các nút khác mà không có. Hãy nhớ rằng, toàn bộ điểm xây dựng trước mạng là để tránh những phức tạp như thế này.

Như thể điều này không đủ, có nhiều lý do khác khiến việc sử dụng hàm lambda không thuận tiện để cung cấp chức năng mong muốn. Bởi vì nó được truyền vào hàm khởi tạo như một đối số, hàm không thể truy cập con trỏ this của cá thể tác vụ cuối cùng, làm cho mã xấu xí, đặc biệt là xem xét lambda nhất thiết được xác định trong phạm vi - và có thể vô tình đóng -some không liên quan đến con trỏ this.

Tôi có thể tiếp tục, nhưng điểm mấu chốt là bạn không cần phải chịu đựng bloat đóng cửa thời gian chạy và các phức tạp khác khi xác định chức năng mở rộng trong lớp dẫn xuất. Điều đó không bỏ lỡ toàn bộ điểm đa hình? Nó sẽ là thanh lịch hơn để xác định các đại biểu công việc của một lớp học Task -derived theo cách thông thường, cụ thể là, một hàm trừu tượng trong lớp cơ sở.

Dưới đây là cách thực hiện. Bí quyết là định nghĩa một hàm tạo riêng tư để đóng một trong các đối số của chính nó. Đối số, ban đầu được đặt thành null, hoạt động như một biến giữ chỗ mà bạn có thể đóng để tạo đại biểu theo yêu cầu của lớp cơ sở Task. Khi bạn đang ở trong phần thân của hàm tạo, con trỏ 'this' có sẵn, vì vậy bạn có thể vá trong con trỏ hàm thực tế.

Đối với phát sinh từ 'Nhiệm vụ':

public abstract class DeferredActionTask : Task 
{ 
    private DeferredActionTask(DeferredActionTask _this) 
     : base(_ => ((Func<DeferredActionTask>)_)().action(), 
       (Func<DeferredActionTask>)(() => _this)) 
    { 
     _this = this; 
    } 
    protected DeferredActionTask() : this(null) { } 

    protected abstract void action(); 
}; 

Đối với phát sinh từ 'Task <TResult>':

public abstract class DeferredFunctionTask<TResult> : Task<TResult> 
{ 
    private DeferredFunctionTask(DeferredFunctionTask<TResult> _this) 
     : base(_ => ((Func<DeferredFunctionTask<TResult>>)_)().function(), 
       (Func<DeferredFunctionTask<TResult>>)(() => _this)) 
    { 
     _this = this; 
    } 
    protected DeferredFunctionTask() : this(null) { } 

    protected abstract TResult function(); 
}; 

[Edit: Giản]

Các phiên bản được đơn giản hóa này tiếp tục giảm các bao đóng không liên quan bằng cách đóng trực tiếp trên các thể hiện bắt buộc 'hành động hoặc chức năng phương pháp'. Điều này cũng giải phóng các AsyncState trong lớp cơ sở trong trường hợp bạn muốn sử dụng nó. Nó hầu như không cần thiết vì bạn có toàn bộ lớp học có nguồn gốc riêng của bạn ngay bây giờ; theo đó, AsyncState không được chuyển vào chức năng làm việc. Nếu bạn cần nó, bạn luôn có thể lấy nó từ tài sản trên lớp cơ sở. Cuối cùng, các tham số tùy chọn khác nhau có thể được chuyển qua lớp cơ sở Task.

Đối với phát sinh từ 'Nhiệm vụ':

public abstract class DeferredActionTask : Task 
{ 
    private DeferredActionTask(Action _a, Object state, CancellationToken ct, TaskCreationOptions opts) 
     : base(_ => _a(), state, ct, opts) 
    { 
     _a = this.action; 
    } 

    protected DeferredActionTask(
      Object state = null, 
      CancellationToken ct = default(CancellationToken), 
      TaskCreationOptions opts = TaskCreationOptions.None) 
     : this(default(Action), state, ct, opts) 
    { 
    } 

    protected abstract void action(); 
}; 

Đối với phát sinh từ 'Task <TResult>':

public abstract class DeferredFunctionTask<TResult> : Task<TResult> 
{ 
    private DeferredFunctionTask(Func<TResult> _f, Object state, CancellationToken ct, TaskCreationOptions opts) 
     : base(_ => _f(), state, ct, opts) 
    { 
     _f = this.function; 
    } 

    protected DeferredFunctionTask(
      Object state = null, 
      CancellationToken ct = default(CancellationToken), 
      TaskCreationOptions opts = TaskCreationOptions.None) 
     : this(default(Func<TResult>), state, ct, opts) 
    { 
    } 

    protected abstract TResult function(); 
}; 
0
private async DeferredFunctionTask<int> WaitForStart(CancellationTokenSource c, string serviceName) 
    { 


     var t = await Task.Run<int>(() => 
     { 
      int ret = 0; 
      for (int i = 0; i < 500000000; i++) 
      { 


       //ret += i; 
       //if (i % 100000 == 0) 
       // Console.WriteLine(i); 

       if (c.IsCancellationRequested) 
       { 
        return ret; 
       } 
      } 

      return ret; 

     }); 


     return t; 
    } 

Lỗi CS1983 Các kiểu trả về của một async phương thức phải là void, Task hoặc Task

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