2016-08-12 15 views
5

Tôi muốn chạy một tác vụ chạy dài (nói 4-5 phút) trong một ApiController trong môi trường OWIN tự lưu trữ. Tuy nhiên tôi muốn gửi một phản hồi lại sau khi bắt đầu nhiệm vụ đó (ngay sau khi tôi bắt đầu nhiệm vụ chạy dài) mà không cần đợi nó kết thúc. Nhiệm vụ chạy dài này không liên quan gì đến HTTP và tuần tự chạy một số phương thức có thể mất rất nhiều thời gian.Tác vụ chạy dài trong ApiController (sử dụng WebAPI, OWIN tự lưu trữ)

Tôi có xem this bài đăng trên blog và quyết định thử xem QueueBackgroundWorkItem. Tuy nhiên, tôi không chắc chắn nếu nó có thể sử dụng phương pháp này trong môi trường nợ tự lưu trữ (giao diện điều khiển ứng dụng) hoặc cần thiết để sử dụng nó. Trong ứng dụng giao diện điều khiển tự lưu trữ, tôi nghĩ, ứng dụng tự quản lý các yêu cầu và tất cả yêu cầu chạy trong cùng một AppDomain (các ứng dụng mặc định AppDomain, chúng tôi không tạo bất kỳ appDomain mới nào), vì vậy có thể tôi chỉ cần chạy một tác vụ chạy dài trong một thời trang lửa-và-quên, mà không làm bất cứ điều gì đặc biệt?

Dù sao, khi tôi sử dụng QueueBackgroundWorkItem, tôi luôn nhận được lỗi:

<Error> 
<Message>An error has occurred.</Message> 
<ExceptionMessage> 
Operation is not valid due to the current state of the object. 
</ExceptionMessage> 
<ExceptionType>System.InvalidOperationException</ExceptionType> 
<StackTrace> 
at System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(Func`2 workItem) at BenchMarkService.SmokeTestController.IsItWorking() in C:\Devel\Code\Projects\BenchMarkService\BenchMarkService\SmokeTestController.cs:line 18 at lambda_method(Closure , Object , Object[]) at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass10.<GetExecutor>b__9(Object instance, Object[] methodParameters) at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.Execute(Object instance, Object[] arguments) at System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken) --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__0.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__2.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__1.MoveNext() 
</StackTrace> 
</Error> 

Ngoài ra tôi thấy this câu hỏi về SO và phải trung thực có vẻ như một chút khó hiểu đối với tôi như là cách duy nhất để đạt được điều đó là IRegisteredObject hay không?

Tôi chỉ đang cố chạy một tác vụ dài trong ứng dụng tự lưu trữ và tôi đánh giá cao bất kỳ ý tưởng nào về điều đó. Hầu như tất cả các tài nguyên, các câu hỏi tôi tìm thấy đều dựa trên asp .net và tôi không chắc bắt đầu từ đâu.

+0

Không phải là một tùy chọn để xếp hàng tác vụ (trong cơ sở dữ liệu) và để một số dịch vụ xử lý nó? –

+0

Hangfire (https://www.hangfire.io/) thực hiện điều gì đó tương tự như tôi biết.Tuy nhiên, tôi tin rằng điều này hơi khác một chút so với câu hỏi. – Deniz

Trả lời

2

ứng dụng tự tổ chức OWIN thường console các ứng dụng thông thường, do đó bạn có thể spin off một số nhiệm vụ dài chạy trong cùng một cách mà bạn sẽ làm điều đó trong một ứng dụng giao diện điều khiển, ví dụ:

  • Task.Run
  • ThreadPool.QueueUserWorkItem
  • new Thread(...).Start()

Trong IIS tổ chức các ứng dụng ASP.NET nó không phải là khuyến khích sử dụng các phương pháp như vậy vì các hồ bơi ứng dụng thường được tái chế, do đó ứng dụng của bạn bị tắt và khởi động lại khá thường xuyên. Trong trường hợp này, tác vụ nền của bạn sẽ bị hủy bỏ. Để ngăn chặn (hoặc trì hoãn), các API như QueueBackgroundWorkItem đã được giới thiệu.

Tuy nhiên, vì bạn đang tự lưu trữ và không chạy trong IIS, bạn chỉ có thể sử dụng các API được liệt kê ở trên.

2

Bạn cần cung cấp cơ chế để thêm tác vụ vào thứ gì đó bên ngoài bộ điều khiển. Thông thường trong các ứng dụng tôi sử dụng owin cho máy chủ thực tế là IIS vì vậy tôi đã tìm ra tôi có thể sử dụng "Hosted Processes" nhưng trong một ứng dụng giao diện điều khiển nó có thể sẽ đơn giản hơn rất nhiều.

Cách tiếp cận rõ ràng nhất mà đến với tâm là để vượt qua trong một số loại toàn cầu đối tượng/singleton rằng khuôn khổ DI bạn biết về có thể chắc chắn luôn là đối tượng giống như một "container cho công việc"

public class FooController : ApiController 
{ 
    ITaskRunner runner; 

    public FooController(ITaskRunner runner) { this.runner = runner; } 

    Public IActionResult DoStuff() { 
     runner.AddTask(() => { Stuff(); }); 
     return Ok(); 
    } 
} 

Á hậu nhiệm vụ có thể thực hiện công việc bằng cách ít hơn một trình bao bọc đơn giản xung quanh Tác vụ.Chạy() nếu bạn muốn, nhưng có móc đó và thông qua có nghĩa là sau khi yêu cầu được "xử lý" và bộ điều khiển được làm sạch, nhiệm vụ vẫn được coi là "trong phạm vi" để ứng dụng có thể tiếp tục xử lý thay vì cố gắng làm sạch nó.

2

Tôi muốn bình luận về câu trả lời của chiến tranh, nhưng trả lời câu hỏi của riêng tôi có vẻ tốt hơn cho một lời giải thích dài. Đây có thể không phải là câu trả lời hoàn chỉnh, xin lỗi.

Giải pháp của chiến tranh là một cách có thể để triển khai tính năng này và tôi đã làm một việc tương tự như vậy. Về cơ bản, tạo một cửa hàng nhiệm vụ và bất cứ khi nào một nhiệm vụ mới được kích hoạt hoặc hoàn thành thêm/xóa nhiệm vụ này vào kho lưu trữ nhiệm vụ. Tuy nhiên, tôi muốn đề cập đến một số vấn đề, bởi vì tôi tin rằng nó không đơn giản như thế.

1) Tôi đồng ý với việc sử dụng khung tiêm phụ thuộc để triển khai mẫu đơn. Tôi đã sử dụng phương pháp 'SingleInstance()' của Autofac để làm điều đó. Tuy nhiên, như bạn có thể thấy trong nhiều câu trả lời và tài nguyên trên Internet, mẫu đơn không phải là mẫu phổ biến mặc dù đôi khi dường như không thể thiếu.

2) Kho lưu trữ của bạn phải an toàn theo luồng, vì vậy việc thêm/xóa nhiệm vụ từ kho lưu trữ tác vụ đó phải an toàn chỉ. Bạn có thể sử dụng một cấu trúc dữ liệu đồng thời như 'ConcurrentDictionary' thay vì khóa (đó là một hoạt động tốn kém về thời gian cpu).

3) Bạn có thể cân nhắc việc sử dụng CancellationToken cho việc không đồng bộ. hoạt động. Rất có thể ứng dụng của bạn có thể bị tắt bởi người dùng hoặc một ngoại lệ có thể được xử lý có thể xảy ra trong thời gian chạy. Các tác vụ chạy dài có thể là một nút cổ chai cho các hoạt động tắt/khởi động duyên dáng, đặc biệt nếu bạn có dữ liệu nhạy cảm bị mất và sử dụng các tài nguyên khác nhau như các tệp có thể được đóng đúng trong trường hợp đó. Mặc dù không đồng bộ. phương pháp có thể không bị hủy ngay lập tức bất cứ khi nào bạn cố gắng hủy tác vụ bằng cách sử dụng mã thông báo, bạn vẫn nên thử dùng CancellationToken hoặc các cơ chế tương tự để dừng/hủy các tác vụ chạy dài của bạn bất cứ khi nào cần thiết.

4) Chỉ cần thêm tác vụ mới vào kho lưu trữ dữ liệu của bạn là không đủ, bạn cũng có thể xóa tác vụ bất cứ khi nào nó hoàn thành dù thành công hay không. Tôi đã thử kích hoạt một sự kiện báo hiệu hoàn thành nhiệm vụ và sau đó tôi đã xóa tác vụ này khỏi cửa hàng tác vụ. Tuy nhiên, tôi không hài lòng với điều đó bởi vì việc sa thải một sự kiện và sau đó ràng buộc một phương pháp cho sự kiện này để nhận được hoàn thành một nhiệm vụ ít nhiều giống như một sự tiếp tục trong TPL. Nó giống như phát minh lại bánh xe và các sự kiện cũng có thể gây ra các vấn đề lén lút trong môi trường đa luồng.

Hy vọng điều này sẽ hữu ích. Tôi thích chia sẻ một số kinh nghiệm thay vì đăng mã, bởi vì tôi không tin rằng tôi có một giải pháp cuối cùng cho vấn đề này. Phản ứng của chiến tranh đưa ra một ý tưởng thô, nhưng bạn cần cân nhắc nhiều vấn đề trước khi thực hiện điều đó trong một hệ thống sẵn sàng sản xuất.

Chỉnh sửa: Đối với các tác vụ chạy dài, bạn có thể xem chủ đề this. Đó là một cách hay để quản lý nhóm luồng.

+0

Yeh suy nghĩ của tôi chính xác ... Tôi đã cố gắng không để bog câu trả lời của tôi xuống với quá nhiều thông tin cơ bản mà có thể hoặc không thể đưa ra giả định về việc thực hiện của bạn. Đối với một giải pháp hoàn chỉnh, bạn muốn tránh làm một cái gì đó khá đơn giản có lợi cho việc đáp ứng các tiêu chuẩn mô hình/mã hóa trong việc khôi phục lại codebase của bạn. Đối với mối quan tâm luồng, cũng lớp dữ liệu của bạn/logic kinh doanh nên xử lý như vậy ở cấp độ này tôi sẽ không được quan tâm về đá ra chủ đề/nhiệm vụ. 2p của tôi ... vui vì bạn đã giải quyết nó mặc dù :) – War

3

Điều này là khá nhiều những gì Hangfire đã được tạo để làm (https://www.hangfire.io/).

Nghe có vẻ như là một công việc dễ cháy.

var jobId = BackgroundJob.Enqueue(
    () => Console.WriteLine("Fire-and-forget!")); 
Các vấn đề liên quan