如何使用Observable.ToAsync与IEnumerable

时间:2011-08-16 15:44:09

标签: c# system.reactive

我遇到了Rx的一些问题。我需要在处理新线程中的每个项目后将结果发送到主线程。我用其他方式做到了。如何用Rx解决这个任务?这是代码:

 Observable.ToAsync<string, IEnumerable<UserState>>(Run)(path)
 .ObserveOnDispatcher<IEnumerable<UserState>>()
 .Subscribe(
(o) =>
{   // need to run in Main Thread
    foreach (var item in o)
    {
     WriteLog(item.StatusCode, item.Url);
    }                        
},
(ex) =>{        },
() =>{      } );

   // need to run in New Thread
   private IEnumerable<UserState> Run(string sitemap)
   {
 ....
 foreach (var url in urls)
 {
    var result = new UserState
    {
        Url = url.Value,
        TotalItems = urls.Count
    };
    ....
    yield return result;
 }
   }

2 个答案:

答案 0 :(得分:1)

如果你能描述一下你的问题,那将是件好事。

我现在可以告诉你的是,你有几个潜在的问题。

首先,使用ObserveOnDispatcher意味着使用System.Windows.Threading.Dispatcher(通常使用WPF创建)。如果您在WPF之外运行代码,它实际上意味着“当前线程”,这可能导致您的订阅无法在当前线程繁忙时运行。换句话说,您可能会创建一个死锁。

我在WPF和LINQPad中运行你的代码,它在WPF中运行良好,但在LINQPad中死锁。如果我在另一个线程上观察到它在LINQPad中工作正常并且在WPF中失败。

其次,您将迭代器方法转换为异步observable,并且无法按预期工作。在实际遍历可枚举之前,迭代器实际上不会运行任何代码。基本上你几乎立即从Run返回,你只在Run代码中执行Subscribe方法的主体 - 这是错误的线程!

您需要做的是强制立即执行可枚举 - 至少 - 将代码更改为:

private UserState[] Run(string sitemap)
{
    ...
    Func</* url type */, UserState> create = url =>
    {
        var result = new UserState
        {
            Url = url.Value,
            TotalItems = urls.Count
        };
        ....
        return result;
    };
    return (from url in urls select create(url)).ToArray();
}

您的主要代码需要进行一些清理:

Observable.ToAsync<string, UserState[]>(Run)(path)
    .ObserveOnDispatcher()
    .Subscribe(o =>
    {
        foreach (var item in o)
        {
            WriteLog(item.StatusCode, item.Url);
        }                        
    });

如果有任何帮助,请告诉我。


编辑:根据评论中的OP请求添加了示例FromEventPattern代码。

以下是Windows窗体使用FromEventPattern的示例。第一部分创建了一种在表单关闭时清理订阅的方法。

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        // Create a collection of IDisposable
        // to allow clean-up of subscriptions
        var subscriptions =
            new System.Reactive.Disposables.CompositeDisposable();

        var formClosings = Observable
            .FromEventPattern<FormClosingEventHandler, FormClosingEventArgs>(
                h => this.FormClosing += h,
                h => this.FormClosing -= h);

        // Add a subscription that cleans up subscriptions
        // when the form closes
        subscriptions.Add(
            formClosings
                .Subscribe(ea => subscriptions.Dispose()));

下一部分会在图片框中监视鼠标拖动并创建消息,让用户知道他们拖动了多远。

        var pictureBox1MouseDowns = Observable
            .FromEventPattern<MouseEventHandler, MouseEventArgs>(
                h => pictureBox1.MouseDown += h,
                h => pictureBox1.MouseDown -= h);

        var pictureBox1MouseMoves = Observable
            .FromEventPattern<MouseEventHandler, MouseEventArgs>(
                h => pictureBox1.MouseMove += h,
                h => pictureBox1.MouseMove -= h);

        var pictureBox1MouseUps = Observable
            .FromEventPattern<MouseEventHandler, MouseEventArgs>(
                h => pictureBox1.MouseUp += h,
                h => pictureBox1.MouseUp -= h);

        var pictureBox1MouseDrags =
            from md in pictureBox1MouseDowns
            from mm in pictureBox1MouseMoves.TakeUntil(pictureBox1MouseUps)
            let dx = mm.EventArgs.Location.X - md.EventArgs.Location.X
            let dy = mm.EventArgs.Location.Y - md.EventArgs.Location.Y
            select new Point(dx, dy);

        var pictureBox1MouseDragMessages =
            from md in pictureBox1MouseDrags
            let f = "You've dragged ({0}, {1}) from your starting point"
            select String.Format(f, md.X, md.Y);

下一部分将跟踪点击按钮的次数,并创建要显示给用户的消息。

        var button1ClickCount = 0;

        var button1Clicks = Observable
            .FromEventPattern(
                h => button1.Click += h,
                h => button1.Click -= h);

        var button1ClickCounts =
            from c in button1Clicks
            select ++button1ClickCount;

        var button1ClickMessages =
            from cc in button1ClickCounts
            let f = "You clicked the button {0} time{1}"
            select String.Format(f, cc, cc == 1 ? "" : "s");

最后,两个消息obervables合并在一起并被订阅,将消息放在标签中。

        var messages = pictureBox1MouseDragMessages
            .Merge(button1ClickMessages);

        // Add a subscription to display the
        // merged messages in the label
        subscriptions.Add(
            messages
                .Subscribe(m => label1.Text = m));
    }
}

请记住,所有这些都驻留在表单的构造函数中,并且不使用任何模块级别字段或属性,并且在表单关闭时将删除所有事件处理程序。很整洁。

答案 1 :(得分:1)

你想在其他一些后台线程上生成IEnumerable,然后在主UI线程中处理这个枚举中的每个用户对象,如果这种理解是正确的,那么你可以这样做:

var users = Run(path); //NOTE: This doesn't execute your run method yet, Run will only execute when you start enumerating the users values
users.ToObservable(System.Concurrency.Scheduler.ThreadPool) //The enumerator will be scheduled on separate thread
.ObserveOn(frm) //Observe on UI thread of win form
.Subscribe(s => {}) //This will run in UI thread for each user object