複数のファイルを Silverlight クライアントから直接 Amazon S3 にアップロードしようとしています。ユーザーは標準のファイルを開くダイアログからファイルを選択し、アップロードを連鎖させて、一度に 1 つずつ順番に実行したいと考えています。これはアプリの複数の場所から発生する可能性があるため、選択したファイルの IEnumerable を受け入れる素敵なユーティリティ クラスにまとめようとして、ファイルがアップロードされるときにファイルの IObservable を公開して、UI が各ファイルに応じて応答できるようにしました。終了しました。
Silverlight と AmazonS3 の両方のすべてのセキュリティ要件のために、これはかなり複雑です。コンテキストのために環境全体を簡単に説明しようとしますが、コードを以下に投稿する小さなコンソール アプリケーションで問題を再現しました。
標準のイベント ベースの非同期メソッドを公開する Silverlight から S3 へのアップロードを処理するサード パーティのユーティリティがあります。アップロードされたファイルごとに、そのユーティリティのインスタンスを 1 つ作成します。署名されていないリクエスト文字列を作成し、それをサーバーに投稿して秘密鍵で署名します。その署名要求は、イベント ベースの非同期メソッドも使用するサービス プロキシ クラスを介して行われます。署名済みのリクエストを取得したら、それをアップローダ インスタンスに追加してアップロードを開始します。
Concat を使用してみましたが、プロセスを通過するのは最初のファイルだけです。Merge を使用すると、すべてのファイルが正常に完了しますが、順次ではなく並列で完了します。Merge(2) を使用すると、すべてのファイルが最初のステップを開始しますが、その後は 2 つだけが通過して完了します。
Rx が期待どおりに動作していないため、明らかに 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();
}
}
}