如何在RxUI中正确实现后端ReactiveList以避免线程关联问题?

时间:2018-02-03 17:07:28

标签: c# wpf multithreading system.reactive reactiveui

我是Rx.Net&的新手。 RxUI。在我学习这两个库的过程中,我尝试构建一个从网站中提取图像的演示应用程序。我使用WPF结合Rx.Net和RxUI来构建Views和ViewModels,并使用HtmlAgilityPack来处理html文档。我的代码如下所示

视图模型:

public class MainViewModel : ReactiveObject
{
    public MainViewModel()
    {
        var canSearch =
            this.WhenAnyValue(x => x.TargetUrl, targetWebSite => !string.IsNullOrEmpty(targetWebSite));
        _searchCommand = ReactiveCommand.CreateFromTask(GetHtmlDocument, canSearch);
        _imageSequence = _searchCommand
            .SelectMany(ImageExtractService.ExtractAllImageAddress).Distinct().Publish().RefCount();
        _imageSequence.Subscribe(
            url => ImageList.Add(new ScrappedWebImageViewModel { ImageUrl = url }),
            ex => this.Log().Error(ex)); //Causing problem, need better solution
    }

    private readonly IObservable<string> _imageSequence;

    private IHtmlDownloadService _htmlDownloadService;

    private IHtmlDownloadService HtmlDownloadService =>
        _htmlDownloadService ?? (_htmlDownloadService = Locator.Current.GetService<IHtmlDownloadService>());

    private IImageExtractService _imageExtractService;

    private IImageExtractService ImageExtractService =>
        _imageExtractService ?? (_imageExtractService = Locator.Current.GetService<IImageExtractService>());

    public ReactiveList<ScrappedWebImageViewModel> ImageList =
        new ReactiveList<ScrappedWebImageViewModel>();

    private readonly ReactiveCommand<Unit, HtmlDocument> _searchCommand;

    public ICommand SearchCommand => _searchCommand;

    private async Task<HtmlDocument> GetHtmlDocument()
    {
        return await HtmlDownloadService.GetHtmlDocument(TargetUrl);
    }
}

查看:

public partial class MainWindow : IViewFor<MainViewModel>
{
    public MainWindow()
    {
        InitializeComponent();

        ViewModel = new MainViewModel();
        this.WhenActivated(d => 
        {
            d(this.Bind(ViewModel, x => x.Status, x => x.TblStatus.Text));
            d(this.Bind(ViewModel, x => x.Progress, x => x.TblProgress.Text));
            d(this.Bind(ViewModel, x => x.TargetUrl, x => x.TbxTargetWebSite.Text));
            d(this.OneWayBind(ViewModel, x => x.ImageList, x => x.LbxImageList.ItemsSource));
            d(this.BindCommand(ViewModel, x => x.SearchCommand, x => x.BtnBeginSearch));
        });
    }

    public static readonly DependencyProperty ViewModelProperty =
        DependencyProperty.Register("ViewModel", typeof(MainViewModel), typeof(MainWindow));

    public MainViewModel ViewModel
    {
        get => (MainViewModel) GetValue(ViewModelProperty);
        set => SetValue(ViewModelProperty, value);
    }

    object IViewFor.ViewModel
    {
        get => ViewModel;
        set => ViewModel = (MainViewModel)value;
    }
}

HtmlDownloadService:

internal class HtmlDownloadService : IHtmlDownloadService
{
    private readonly HtmlWeb _webClient = new HtmlWeb();

    public async Task<HtmlDocument> GetHtmlDocument(string url)
    {
        return await Task.Run(() => _webClient.Load(url));
    }
}

ImageExtractService:

internal class ImageExtractService : IImageExtractService
{
    public IEnumerable<string> ExtractAllImageAddress(HtmlDocument doc)
    {
        const string mstring = @".+\.(jpg|png|ico|jpeg|bmp|tif)$";
        var hrefList = doc.DocumentNode.SelectNodes(@".//*[@href]");
        var srcList = doc.DocumentNode.SelectNodes(@".//*[@src]");
        if (hrefList != null)
        {
            foreach (var href in hrefList)
            {
                var attr = href.Attributes["href"];
                if (Regex.IsMatch(attr.Value, mstring))
                {
                    yield return attr.Value;
                }
            }
        }

        if (srcList == null) yield break;
        foreach (var src in srcList)
        {
            var attr = src.Attributes["src"];
            if (Regex.IsMatch(attr.Value, mstring))
            {
                yield return attr.Value;
            }
        }
    }
}

问题是,在执行命令后,应用程序将暂停。此时主线程正在

中运行
  

System.Reactive.dll!System.Reactive.Concurrency.AsyncLock.Wait

但是没有抛出异常,如果允许继续,应用程序将退出。我试图引用/取消引用几行,而这似乎只是&#39;线程关联性的另一个实例。问题。但我不知道如何解决这个问题。简而言之,我的问题是:

  1. 如何以最合适的方式更新VM?
  2. 如何捕获关闭应用程序的异常?
  3. 更新

    我尝试了一些没有observables的其他方法

    public MainViewModel()
    {
        var canSearch =
            this.WhenAnyValue(x => x.TargetUrl, targetWebSite => !string.IsNullOrEmpty(targetWebSite));
        SearchCommand = ReactiveCommand.CreateFromTask(SearchImageAsync, canSearch, ThreadPoolScheduler.Instance);
    }
    

    private async Task SearchImageAsync()
    {
        var doc = await HtmlDownloadService.GetHtmlDocument(TargetUrl);
        var imgs = ImageExtractService.ExtractAllImageAddress(doc);
        foreach (var url in imgs)
        {
            ImageList.Add(new ScrappedWebImageViewModel {ImageUrl = url});
        }
    }
    

    但仍然无法解决它。我使用的是最新的不稳定(alpha / preview)版本的Rx.Net&amp; RxUI,我开始使用的示例代码很少。所以,如果有人能提供一些,那将是一个巨大的帮助,谢谢!

1 个答案:

答案 0 :(得分:2)

更改

_imageSequence.Subscribe(

_imageSequence.ObserveOn(RxApp.MainThreadScheduler).Subscribe(