2010-09-04 14 views
7

Tôi đang viết một lớp trợ giúp không đồng bộ rất đơn giản để đi cùng với dự án của tôi. Mục đích của lớp là nó cho phép một phương thức được chạy trên một luồng nền. Đây là mã;C# gọi lại async vẫn còn trên nền chủ đề ... giúp đỡ! (tốt nhất là không có InvokeRequired)


    internal class AsyncHelper 
    { 
     private readonly Stopwatch timer = new Stopwatch(); 
     internal event DownloadCompleteHandler OnOperationComplete; 

     internal void Start(Func func, T arg) 
     { 
      timer.Start(); 
      func.BeginInvoke(Done, func); 
     } 

     private void Done(IAsyncResult cookie) 
     { 
      timer.Stop(); 
      var target = (Func) cookie.AsyncState; 
      InvokeCompleteEventArgs(target.EndInvoke(cookie)); 
     } 

     private void InvokeCompleteEventArgs(T result) 
     { 
      var args = new EventArgs(result, null, AsyncMethod.GetEventByClass, timer.Elapsed); 
      if (OnOperationComplete != null) OnOperationComplete(null, args); 
     } 

     #region Nested type: DownloadCompleteHandler 

     internal delegate void DownloadCompleteHandler(object sender, EventArgs e); 

     #endregion 
    } 

Kết quả của tác vụ sau đó được trả về thông qua sự kiện OnOperationComplete. Vấn đề là khi sự kiện được nâng lên, nó vẫn còn trên chủ đề nền. I E. nếu tôi cố gắng để chạy mã này (dưới đây) tôi nhận được một lỗi luồng chéo;

txtOutput.AppendText(e.Result + Environment.NewLine);

Vui lòng thông báo cho mọi suy nghĩ.

+0

Bạn có đang sử dụng .NET 2.0, 3.5, 4.0 không? Và bạn đang tạo một ứng dụng WinForms, WPF, Silverlight? –

+0

.NET 4.0, thư viện lớp học –

Trả lời

5

Sử dụng lớp BackgroundWorker. Về cơ bản nó giống như bạn muốn.

 private BackgroundWorker _worker; 

    public Form1() 
    { 
     InitializeComponent(); 
     _worker = new BackgroundWorker(); 
     _worker.DoWork += Worker_DoWork; 
     _worker.RunWorkerCompleted += Work_Completed; 
    } 

    private void Work_Completed(object sender, RunWorkerCompletedEventArgs e) 
    { 
     txtOutput.Text = e.Result.ToString(); 
    } 

    private void Worker_DoWork(object sender, DoWorkEventArgs e) 
    { 
     e.Result = "Text received from long runing operation"; 
    } 
+0

Bạn cũng có thể báo cáo cài đặt tiến trình: _worker.WorkerReportsProgress = true; // AND _worker.ProgressChanged + = Progress_Changed; –

+0

Tôi đã quên ... để thực sự chạy công nhân, bạn phải gọi phương thức _worker.RunWorkerAsync(). –

+0

Có một liên kết “chỉnh sửa” nhỏ bên dưới câu trả lời của bạn. :) – Timwi

0

Bạn cần phải gọi sự kiện của bạn trên thread UI,

WinForms

Form1.BeginInvoke(...);

WPF

Dispatcher.BeginInvoke(...);

+0

Bạn có đề xuất điều này không ?; OnOperationComplete.BeginInvoke

+0

'ISynchronizeInvoke' là giao diện cũ và không nên sử dụng. –

+1

@Jon, không, anh ấy đề nghị bạn gọi phương thức BeginInvoke hoặc Invoke được tìm thấy trên một điều khiển trực quan. Bằng cách đó, bạn sẽ sắp xếp cuộc gọi qua luồng GUI và điều đó sẽ cho phép bạn thao tác trạng thái GUI. 'Delegate.BeginInvoke' và' Control.BeginInvoke' không giống nhau. –

0

Sử dụng lớp BackgroundWorker, bạn về cơ bản reimplementing nó đây.

0

Trừ khi bạn xây dựng lớp giúp đỡ của bạn để làm bối cảnh chuyển trong nội bộ bạn sẽ luôn luôn cần phải gọi trong xử lý sự kiện bởi vì trong mã của bạn ở trên, bạn đang làm dấy lên sự kiện trên thread phi ui.

Để làm điều đó, người trợ giúp của bạn cần phải biết cách lấy lại chuỗi ui. Bạn có thể chuyển số ISynchronizeInvoke cho người trợ giúp và sau đó sử dụng nó khi hoàn tất. Somwthing như:

ISynchronizeInvoke _sync; 
internal void Start(Func func, T arg, ISynchronizeInvoke sync) 
{ 
    timer.Start(); 
    func.BeginInvoke(Done, func); 
    _sync = sync; 
} 

private void InvokeCompleteEventArgs(T result) 
{ 
    var args = new EventArgs(result, null, AsyncMethod.GetEventByClass, timer.Elapsed); 
    if (OnOperationComplete != null) 
    _sync.Invoke(OnOperationComplete, new object[]{null, args}); 
} 

Lớp Control thực hiện ISynchronizeInvoke để bạn có thể vượt qua con trỏ this từ Form hoặc Control mà các cuộc gọi helper và có các đại biểu xử lý sự kiện,

+0

'ISynchronizeInvoke' là một giao diện lỗi thời và không nên được sử dụng. –

+0

@Stephen - tham chiếu? – dkackman

3

tôi khuyên bạn nên sử dụng các lớp chứ không phải TaskBackgroundWorker, nhưng either would be greatly superior to Control.Invoke or Dispatcher.Invoke.

Ví dụ:

internal class AsyncHelper<T> 
{ 
    private readonly Stopwatch timer = new Stopwatch(); 
    private readonly TaskScheduler ui; 

    // This should be called from a UI thread. 
    internal AsyncHelper() 
    { 
    this.ui = TaskScheduler.FromCurrentSynchronizationContext(); 
    } 

    internal event DownloadCompleteHandler OnOperationComplete; 

    internal Task Start(Func<T> func) 
    { 
    timer.Start(); 
    Task.Factory.StartNew(func).ContinueWith(this.Done, this.ui); 
    } 

    private void Done(Task<T> task) 
    { 
    timer.Stop(); 
    if (task.Exception != null) 
    { 
     // handle error condition 
    } 
    else 
    { 
     InvokeCompleteEventArgs(task.Result); 
    } 
    } 

    private void InvokeCompleteEventArgs(T result) 
    { 
    var args = new EventArgs(result, null, AsyncMethod.GetEventByClass, timer.Elapsed); 
    if (OnOperationComplete != null) OnOperationComplete(null, args); 
    } 

    internal delegate void DownloadCompleteHandler(object sender, EventArgs e); 
} 

Điều này rất giống với BackgroundWorker, mặc dù (ngoại trừ bạn đang thêm bộ hẹn giờ). Bạn có thể muốn xem xét chỉ sử dụng BackgroundWorker.

+0

Bạn đã đọc nó chưa? "Ba trang" của tôi bao gồm một giải pháp hoàn chỉnh với giao diện người dùng, hỗ trợ hủy, sửa lỗi chính xác, v.v. Không có giải pháp nào của bạn bao gồm. –

+0

Tôi đã cập nhật câu trả lời của mình bằng một giải pháp đơn giản. –

+0

Tôi thích điều này nhưng không chắc chắn làm thế nào để thực hiện, bạn có thể cho ví dụ sử dụng? –

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