使用Rx链接多个2步文件上传

时间:2012-06-08 16:50:29

标签: system.reactive

我正在尝试将多个文件从Silverlight客户端直接上传到Amazon S3。用户从标准文件打开对话框中选择文件,我想链接上传,以便它们一次串行发生一次。这可能发生在应用程序中的多个位置,所以我试图将其包装在一个很好的实用程序类中,该类接受所选文件的IEnumerable在上传时公开文件的IObservable,以便UI可以相应地响应每个文件完了。

由于Silverlight和AmazonS3的所有安全要求,它相当复杂。我将尝试简要解释我的整个上下文环境,但我已经用一个小型控制台应用程序重现了这个问题,我将把代码发布到下面。

我有一个第三方实用程序,它处理从Silverlight上传到S3的公开标准事件的异步方法。我为每个上传的文件创建一个该实用程序的实例。它创建一个未签名的请求字符串,然后我发布到我的服务器以使用我的私钥进行签名。该签名请求通过服务代理类发生,该服务代理类也使用基于事件的异步方法。获得签名请求后,我将其添加到上传器实例并启动上传。

我尝试过使用Concat,但最终我只得到了第一个文件。当我使用Merge时,所有文件都很好,但是以并行方式而不是串行方式。当我使用Merge(2)时,所有文件都从第一步开始,但只有2个文件完成并完成。

显然我错过了与Rx相关的东西,因为它不像我期望的那样。

namespace RxConcat
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reactive.Linq;
    using System.Timers;

    public class SignCompletedEventArgs : EventArgs
    {
        public string SignedRequest { get; set; }
    }

    public class ChainUploader
    {
        public IObservable<string> StartUploading(IEnumerable<string> files)
        {
            return files.Select(
                     file => from signArgs in this.Sign(file + "_request")
                             from uploadArgs in this.Upload(file, signArgs.EventArgs.SignedRequest)
                             select file).Concat();
        }

        private IObservable<System.Reactive.EventPattern<SignCompletedEventArgs>> Sign(string request)
        {
            Console.WriteLine("Signing request '" + request + "'");
            var signer = new Signer();
            var source = Observable.FromEventPattern<SignCompletedEventArgs>(ev => signer.SignCompleted += ev, ev => signer.SignCompleted -= ev);
            signer.SignAsync(request);
            return source;
        }

        private IObservable<System.Reactive.EventPattern<EventArgs>> Upload(string file, string signedRequest)
        {
            Console.WriteLine("Uploading file '" + file + "'");
            var uploader = new Uploader();
            var source = Observable.FromEventPattern<EventArgs>(ev => uploader.UploadCompleted += ev, ev => uploader.UploadCompleted -= ev);
            uploader.UploadAsync(file, signedRequest);
            return source;
        }
    }

    public class Signer
    {
        public event EventHandler<SignCompletedEventArgs> SignCompleted;

        public void SignAsync(string request)
        {
            var timer = new Timer(1000);
            timer.Elapsed += (sender, args) =>
            {
                timer.Stop();
                if (this.SignCompleted == null)
                {
                    return;
                }

                this.SignCompleted(this, new SignCompletedEventArgs { SignedRequest = request + "signed" });
            };
            timer.Start();
        }
    }

    public class Uploader
    {
        public event EventHandler<EventArgs> UploadCompleted;

        public void UploadAsync(string file, string signedRequest)
        {
            var timer = new Timer(1000);
            timer.Elapsed += (sender, args) =>
            {
                timer.Stop();
                if (this.UploadCompleted == null)
                {
                    return;
                }

                this.UploadCompleted(this, new EventArgs());
            };
            timer.Start();
        }
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            var files = new[] { "foo", "bar", "baz" };
            var uploader = new ChainUploader();
            var token = uploader.StartUploading(files).Subscribe(file =>   Console.WriteLine("Upload completed for '" + file + "'"));
            Console.ReadLine();
        }
    }
}

1 个答案:

答案 0 :(得分:1)

处理每个文件的2步上传的基本observable永远不会“完成”,这会阻止链中的下一个文件启动。

在调用Concat()之前向该observable添加一个Limit(1),它将正常工作。

return files.Select(file => (from signArgs in this.Sign(file + "_request")
                             from uploadArgs in this.Upload(file, signArgs.EventArgs.SignedRequest)
                             select file).Take(1)).Concat();