如何使用最少量的重复代码编写异步类?

时间:2010-01-10 23:53:54

标签: c# asp.net asp.net-mvc winforms asynchronous

我如何编写像WebClient这样的异步类?

无论如何,我可以保持简短,不必为每种方法重复它吗?

例如,我有:

Download(string a)
Download(string a, string b)

我是否必须为每个方法重写Async + Complete方法?

非常感谢。

4 个答案:

答案 0 :(得分:3)

更新

这个例子适用于winforms,我相信OP正在询问asp.net。一旦我从评论中得到一些反馈,我就会修改我的答案。


你猜对了。我发现.Net中一种优雅的多线程代码方式是使用BackgroundWorker类。与在原生C ++中实现线程相比,它非常简单。

MSDN页面上的示例说实话可能有点压倒性,你可以这样总结(伪代码 - 可能无法编译):

private BackgroundWorker bw;

private void foobar() {
  bw = new BackgroundWorker(); // should be called once, in ctor
  bw.DoWork += new DoWorkEventHandler(bw_DoWork);
  bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_Completed);

  int i = 0;
  bw.RunWorkerAsync(i);
}

private void bw_DoWork(object sender, DoWorkEventArgs e) {
  int i = (int)e.Argument;
  i++;
  e.Result = i;
}

private void bw_Completed(object sender, RunWorkerCompletedEventArgs e) {
  if (e.Error != null) {
    MessageBox.Show(e.Error.Message);
  } else {
    int i = (int)e.Result;
    MessageBox.Show(i.ToString());
  }
}

至于将重复的代码保持在最低限度,你可以使用相同的后台工作程序来实现我想要的所有方法,但是你可能正在使用一个闪亮的金色锤子进入臭代码领域。只提供一些方法,然后为每个操作创建事件处理程序不应该看起来太乱。

答案 1 :(得分:1)

如果要异步调用同步方法,则需要使用委托。 http://msdn.microsoft.com/en-us/library/2e08f6yc.aspx。使用这种方法,您不必创建Async和Complete方法。

您描述的模式是使用IAsyncResult,在这种情况下您需要实现IAsyncResult(您也可以根据您的要求使用Framework中的那个)。在您的课程中导出并实现您提到的Async和Complete方法。

答案 2 :(得分:1)

听起来您正在询问实现类的最佳实践,该类公开了具有类似于WebClient的异步版本的方法?

值得注意的是,.NET框架中通常使用两种模式来支持异步使用。例如,您通常会看到像Stream这样的类公开BeginRead和EndRead。这些使用IAsyncResult并且使用起来往往更复杂。 (Overview on MSDN

然后他们提出了event-based async pattern,例如WebClient使用的那个,你有一个DownloadString / DownloadStringAsync方法和一个相应的DownloadStringCompleted事件。对于简单的情况,这更容易使用,但在我看来,它不像开始/结束模式那样灵活。

因此,在您的示例中,您有两个重载方法。在这种情况下,我只是设计它们,使得参数较少的方法遵循方法,更多参数根据需要传递默认值或空值。你可以有多个Download / DownloadAsync重载但你只能有一个DownloadCompleted事件。

这是一个非常基本的实现。请注意,实际执行任何工作的唯一方法是一个同步下载方法。同样重要的是要注意,这也不是最有效的方法。如果您想利用HttpWebRequest的异步IO功能等,这个例子会很快变得复杂。还有一个过于复杂的Async Operation模式,我从来都不喜欢它。

class Downloader {

    public void Download(string url, string localPath) {
        if (localPath == null) {
            localPath = Environment.CurrentDirectory;
        }
        // implement blocking download code
    }

    public void Download(string url) {
        Download(url, null);
    }

    public void DownloadAsync(string url, string localPath) {

        ThreadPool.QueueUserWorkItem( state => {

            // call the sync version using your favorite
            // threading api (threadpool, tasks, delegate.begininvoke, etc)
            Download(url, localPath);

            // synchronizationcontext lets us raise the event back on
            // the UI thread without worrying about winforms vs wpf, etc
            SynchronizationContext.Current.Post( OnDownloadCompleted, null );

        });

    }

    public void DownloadAsync(string url) {
        DownloadAsync(url, null);
    }

    private void OnDownloadCompleted(object state) {
        var handler = DownloadCompleted;
        if (handler != null) {
            handler(this, EventArgs.Empty);
        }
    }

    public event EventHandler DownloadCompleted;

}

答案 3 :(得分:0)

使用MethodInvoker.BeginInvoke调用新的thead。