【问题标题】:AsyncCallback to BackgroundWorkerAsyncCallback 到 BackgroundWorker
【发布时间】:2014-01-30 06:53:02
【问题描述】:

我想使用 .NET-FTP 库 (http://netftp.codeplex.com)。该库提供 BeginOpenRead(string,AsyncCallback,object) 以使用异步编程模型下载内容。我对Callback的实现和例子基本一样:

static void BeginOpenReadCallback(IAsyncResult ar) {
        FtpClient conn = ar.AsyncState as FtpClient;

        try {
            if (conn == null)
                throw new InvalidOperationException("The FtpControlConnection object is null!");

            using (Stream istream = conn.EndOpenRead(ar)) {
                byte[] buf = new byte[8192];

                try {
                    DateTime start = DateTime.Now;

                    while (istream.Read(buf, 0, buf.Length) > 0) {
                        double perc = 0;

                        if (istream.Length > 0)
                            perc = (double)istream.Position / (double)istream.Length;

                        Console.Write("\rTransferring: {0}/{1} {2}/s {3:p}         ",
                                      istream.Position.FormatBytes(),
                                      istream.Length.FormatBytes(),
                                      (istream.Position / DateTime.Now.Subtract(start).TotalSeconds).FormatBytes(),
                                      perc);
                    }
                }
                finally {
                    Console.WriteLine();
                    istream.Close();
                }
            }
        }
        catch (Exception ex) {
            Console.WriteLine(ex.ToString());
        }
        finally {
            m_reset.Set();
        }
    }

在异步方法的工作完成后,如果触发一个 Completed 事件(由启动异步方法的线程以使 UI 没有问题)将结果传递给 Main- 那就太好了-线。就像 BackgroundWorker 一样(使用 RunWorkerCompleted)。

我怎样才能意识到这一点?

【问题讨论】:

  • 您使用的是哪个版本的 .NET?您的选项在 3.5、4 和 4.5 版本之间有所不同。
  • 您需要知道要调用哪个线程,现在您不知道并且在发布的代码中没有可以找到的地方。 BackgroundWorker 通过在其 RunWorkerAsync() 方法中复制 SynchronizationContext.Current 来完成此操作,然后使用其 Post() 方法调用回来。你必须在这个库中找到一个类似的地方,你可以在那里制作副本。或者只是让 UI 来调用它,它从不猜测如何正确执行它。
  • @0xDEADBEEF,如果您在 VS2012+ 中工作,您仍然可以使用 Microsoft.Bcl.Async 定位 .NET 4.0 并使用现代 TPL 功能。

标签: c# .net multithreading ftp backgroundworker


【解决方案1】:

尝试将APM 模式转换为TAP 模式(more info):

static public Task<Stream> OpenReadAsync(FtpClient ftpClient, string url)
{
    return Task.Factory.FromAsync(
         (asyncCallback, state) =>
             ftpClient.BeginOpenRead(url, asyncCallback, state),
         (asyncResult) =>
             ftpClient.EndOpenRead((asyncResult));
}

那你就可以使用async/await,不用担心同步上下文了:

Stream istream = await OpenReadAsync(ftpClient, url); 

另外,你可以使用Stream.ReadAsync:

while (await istream.ReadAsync(buf, 0, buf.Length) > 0) 
{
    // ...
}

BackgroundWorker 已被基于任务的 API 取代,因此可能是双赢的局面(更多信息:Task.Run vs BackgroundWorkerhere)。

[更新]如果您在 VS2012+ 中工作,您可以使用 Microsoft.Bcl.Async 定位 .NET 4.0,并且仍然使用现代语言和 TPL 功能,例如 async/await。我已经经历过,我强烈推荐它,因为它让未来移植到 .NET 4.5 变得轻而易举。

否则,您可以使用Task.ContinueWith(callback, TaskScheduler.FromCurrentSynchronizationContext()) 继续处理 UI 线程。这是related example

【讨论】:

    【解决方案2】:

    最简单的方法是将SynchronizationContext 传递给BeginOpenRead 并在回调中使用它。

    private class StateHolder
    {
        public StateHolder(FtpClient client, SynchronizationContext context)
        {
            Client = client;
            Context = context;
    
            //SynchronizationContext.Current can return null, this creates a new context that posts to the Thread Pool if called.
            if(Context == null)
                Context = new SynchronizationContext();
        }
    
        public FtpClient Client {get; private set;}
        public SynchronizationContext Context {get; private set;}
    }
    
    //...
    
    ftpClient.BeginOpenRead(someString,BeginOpenReadCallback, new StateHolder(ftpClient, SynchronizationContext.Current));
    

    然后在你的回调中使用你传入的那个状态对象。

    void BeginOpenReadCallback(IAsyncResult ar) 
    {
        StateHolder state = ar.AsyncState as StateHolder;
        FtpClient conn = state.client;
    
        //... Everything else the same in the function.
    
        //state.Context can't be null because we set it in the constructor.
        state.Context.Post(OnCompleted, conn);
    
    }
    
    protected virtual void OnCompleted(object state) //I use object instead of FtpClient to make the "state.Context.Post(OnCompleted, conn);" call simpler.
    {
        var conn = state as FtpClient;
        var tmp = Completed; //This is the event people subscribed to.
        (tmp != null)
        {
            tmp(this, new CompletedArgs(conn)); //Assumes you followed the standard Event pattern and created the necessary classes.
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2017-04-20
      • 1970-01-01
      • 2013-07-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多