locked
ThreadPool - как правильно работать с потоками RRS feed

  • Вопрос

  • Здравствуйте. Столкнулся с непонятной мне проблемой. Решил переписать вызов асинхронных функций в виде вызова метода ThreadPool.RunAsync(). Но по какой-то причине такой подход не работает.

    Вот Асинхронные методы, которые я вызываю

    private async Task BashDownoad()
            {
                IsBusy = true;
                var feedClient = new SyndicationClient();
                SyndicationFeed feed = await feedClient.RetrieveFeedAsync(new Uri("http://bash.im/rss/"));
                var bashCollection = new ObservableCollection<Item>();
                foreach (SyndicationItem syndicationItem in feed.Items)
                {
                    bashCollection.Add(new Item
                    {
                        Title = syndicationItem.Title.Text,
                        Summary = syndicationItem.Summary.Text,
                        PublishedDate = syndicationItem.PublishedDate.Date,
                        Id = syndicationItem.Id,
                        Url = syndicationItem.Links[0].Uri.AbsoluteUri,
                        Type = "bash"
                    });
                }
    
                BashItems = bashCollection;
                IsBusy = false;
            }
    
            private async Task DownloadComics()
            {
                var feedClient = new SyndicationClient();
                SyndicationFeed feed = await feedClient.RetrieveFeedAsync(new Uri("http://bash.im/rss/comics.xml"));
    
                var comicsCollection = new ObservableCollection<Item>();
                foreach (SyndicationItem syndicationItem in feed.Items)
                {
                    comicsCollection.Add(new Item
                    {
                        Title = syndicationItem.Title.Text,
                        Url = syndicationItem.Summary.Text.Split('"')[1],
                        PublishedDate = syndicationItem.PublishedDate.DateTime,
                        Id = syndicationItem.Id,
                        Type = "comics"
                    });
                }
    
                ComicsItems = comicsCollection;
            }

    Вот Вызов этих методов

    //BashDownoad();
                ThreadPool.RunAsync(sender => BashDownoad(), WorkItemPriority.Normal);
                //DownloadComics();
                ThreadPool.RunAsync(sender => DownloadComics(), WorkItemPriority.Normal);

    BashItems и ComicsItems - это проперти, isBusy - флаг, по которому ProgressRing в интерфейсе работает.

    private ObservableCollection<Item> _bashItems;
            private ObservableCollection<Item> _comicsItems;
            private bool _isBusy;
            private bool _isFree;
    
            /// <summary>
            ///     Указывает на занятость потока
            /// </summary>
            public bool IsBusy
            {
                get { return _isBusy; }
                set
                {
                    if (_isBusy == value)
                        return;
    
                    _isBusy = value;
                    RaisePropertyChanged();
    
                    if (IsFree == _isBusy)
                        IsFree = !_isBusy;
                }
            }
    
            /// <summary>
            ///     Указывает на свободность потока
            /// </summary>
            public bool IsFree
            {
                get { return _isFree; }
                set
                {
                    if (_isFree == value)
                        return;
    
                    _isFree = value;
                    RaisePropertyChanged();
    
                    if (IsBusy == _isFree)
                        IsBusy = !_isFree;
                }
            }
    
            public ObservableCollection<Item> BashItems
            {
                get { return _bashItems; }
                set
                {
                    if (value == null || value == _bashItems)
                        return;
                    _bashItems = value;
                    RaisePropertyChanged();
                }
            }
    
            public ObservableCollection<Item> ComicsItems
            {
                get { return _comicsItems; }
                set
                {
                    if (value == null || value == _comicsItems)
                        return;
                    _comicsItems = value;
                    RaisePropertyChanged();
                }
            }

    Самое странное, что вызов функции ComicsDownload() проходит успешно и завершается, а метод DownloadBash() прекращается на строчке BasIhtems = BashCollection - ну во всяком случае отладчик тут останавливается.

    Помогите пожалуйста. Заранее благодарю

    3 февраля 2014 г. 6:37

Ответы

  • Смотрите, у Вас есть свойство BashItems. Вы когда в него что-то записываете, происходит RaisePropertyChanged. Это действие должно происходить в  UI потоке, в рабочем потоке обновлять UI нельзя. Соответственно если Вы отдаете выполнение на рабочий поток (Task.Run(код)), то в UI Вы ничего получить не сможете.

    http://social.msdn.microsoft.com/Forums/ru-RU/96df1d03-41ad-4fc3-a78c-e8018d040b95/-?forum=programminglanguageru

    http://social.msdn.microsoft.com/Forums/ru-RU/dd3e5308-78ed-40ea-b644-66109473eeca/-unauthorizedaccessexception?forum=formobiledevicesru



    • Изменено Oleg Kurzov 3 февраля 2014 г. 8:27
    • Помечено в качестве ответа evgeniy.polonskiy 3 февраля 2014 г. 11:36
    3 февраля 2014 г. 8:24
  • Вам пример кода на синхронизацию ?

    И почему это Task.Run не будет работать с методом Task?

    Ведь  Task это и есть возвращаемый тип, а если пересмотреть перегрузки Task.Run, то он, помимо Action. принимает еще Func. Соответственно мы можем написать

    Task.Run(new Func<Task>(this.DownloadComics));

    • Помечено в качестве ответа evgeniy.polonskiy 3 февраля 2014 г. 11:36
    3 февраля 2014 г. 10:06
  • А, я допер

    private async Task BashDownoad()
            {
                IsBusy = true;
                var feedClient = new SyndicationClient();
                SyndicationFeed feed = await feedClient.RetrieveFeedAsync(new Uri("http://bash.im/rss/"));
                var bashCollection = new ObservableCollection<Item>();
                foreach (SyndicationItem syndicationItem in feed.Items)
                {
                    bashCollection.Add(new Item
                    {
                        Title = syndicationItem.Title.Text,
                        Summary = syndicationItem.Summary.Text,
                        PublishedDate = syndicationItem.PublishedDate.Date,
                        Id = syndicationItem.Id,
                        Url = syndicationItem.Links[0].Uri.AbsoluteUri,
                        Type = "bash"
                    });
                }
    
                CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.High,
                    new DispatchedHandler(() =>
                    {
                       BashItems = bashCollection;
                       IsBusy = false;
                    }));
                //BashItems = bashCollection;
                //IsBusy = false;
            }

    Но только правильно ли такое делать?

    • Помечено в качестве ответа evgeniy.polonskiy 3 февраля 2014 г. 11:36
    3 февраля 2014 г. 10:50
  • Я это делаю вот так:

    1. создаю класс, называю его SynchronizationContextProvider.

    2. Вот его код:

    public static class SynchronizationContextProvider
    	{
    		private static SynchronizationContext syncContext;
    
    		public static SynchronizationContext UIThreadSyncContext
    		{
    			get { return syncContext; }
    			set { syncContext = value; }
    		}
    
    		public static void Initialize()
    		{
    			syncContext = SynchronizationContext.Current;
    		}
    	}

    3. В App.xaml.cs , у меня методы OnLanuched, OnSearchActivated, OnFileActivated и тд вызывают 1 общий метод, там я задаю контекст для синхронизации вот так:

    SynchronizationContextProvider.Initialize();

    4. пример использования:

    SynchronizationContextProvider.UIThreadSyncContext.Post((state) =>
                                {
                                    this.BashItems = bashCollection;
                                }, null);

    5. Если нужно делать await в UI потоке, то так пишем:

     SynchronizationContextProvider.UIThreadSyncContext.Post(async (state) =>
                                {
                                    await Task.Delay(10);
                                }, null);
    6. Этот код можно юзать и в ВП проектах, только вместо метода Post, вызываем метод Send.

    • Помечено в качестве ответа evgeniy.polonskiy 3 февраля 2014 г. 11:36
    3 февраля 2014 г. 11:05

Все ответы

  • Task.Run это обертка над ThreadPool и соответственно запускает выполнение в нем, так что  можете использовать его.

    Так же, Вам необходимо будет синхронизировать потоки, когда будете делать RaisePropertyChanged свойств, что бы данные в UI попали.


    3 февраля 2014 г. 7:57
  • Спасибо.

    Про синхронизацию не понял. Зачем это сейчас делать?

    3 февраля 2014 г. 8:15
  • Смотрите, у Вас есть свойство BashItems. Вы когда в него что-то записываете, происходит RaisePropertyChanged. Это действие должно происходить в  UI потоке, в рабочем потоке обновлять UI нельзя. Соответственно если Вы отдаете выполнение на рабочий поток (Task.Run(код)), то в UI Вы ничего получить не сможете.

    http://social.msdn.microsoft.com/Forums/ru-RU/96df1d03-41ad-4fc3-a78c-e8018d040b95/-?forum=programminglanguageru

    http://social.msdn.microsoft.com/Forums/ru-RU/dd3e5308-78ed-40ea-b644-66109473eeca/-unauthorizedaccessexception?forum=formobiledevicesru



    • Изменено Oleg Kurzov 3 февраля 2014 г. 8:27
    • Помечено в качестве ответа evgeniy.polonskiy 3 февраля 2014 г. 11:36
    3 февраля 2014 г. 8:24
  • Олег, а можно любой простой пример кода?

    И еще: я читал, что если асинхронный метод не имеет возвращаемого значения, то лучше определять его не как void, а как Task. Но в таком случае Task.Run() работать не будет

    3 февраля 2014 г. 9:36
  • Вам пример кода на синхронизацию ?

    И почему это Task.Run не будет работать с методом Task?

    Ведь  Task это и есть возвращаемый тип, а если пересмотреть перегрузки Task.Run, то он, помимо Action. принимает еще Func. Соответственно мы можем написать

    Task.Run(new Func<Task>(this.DownloadComics));

    • Помечено в качестве ответа evgeniy.polonskiy 3 февраля 2014 г. 11:36
    3 февраля 2014 г. 10:06
  • А, я допер

    private async Task BashDownoad()
            {
                IsBusy = true;
                var feedClient = new SyndicationClient();
                SyndicationFeed feed = await feedClient.RetrieveFeedAsync(new Uri("http://bash.im/rss/"));
                var bashCollection = new ObservableCollection<Item>();
                foreach (SyndicationItem syndicationItem in feed.Items)
                {
                    bashCollection.Add(new Item
                    {
                        Title = syndicationItem.Title.Text,
                        Summary = syndicationItem.Summary.Text,
                        PublishedDate = syndicationItem.PublishedDate.Date,
                        Id = syndicationItem.Id,
                        Url = syndicationItem.Links[0].Uri.AbsoluteUri,
                        Type = "bash"
                    });
                }
    
                CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.High,
                    new DispatchedHandler(() =>
                    {
                       BashItems = bashCollection;
                       IsBusy = false;
                    }));
                //BashItems = bashCollection;
                //IsBusy = false;
            }

    Но только правильно ли такое делать?

    • Помечено в качестве ответа evgeniy.polonskiy 3 февраля 2014 г. 11:36
    3 февраля 2014 г. 10:50
  • Я это делаю вот так:

    1. создаю класс, называю его SynchronizationContextProvider.

    2. Вот его код:

    public static class SynchronizationContextProvider
    	{
    		private static SynchronizationContext syncContext;
    
    		public static SynchronizationContext UIThreadSyncContext
    		{
    			get { return syncContext; }
    			set { syncContext = value; }
    		}
    
    		public static void Initialize()
    		{
    			syncContext = SynchronizationContext.Current;
    		}
    	}

    3. В App.xaml.cs , у меня методы OnLanuched, OnSearchActivated, OnFileActivated и тд вызывают 1 общий метод, там я задаю контекст для синхронизации вот так:

    SynchronizationContextProvider.Initialize();

    4. пример использования:

    SynchronizationContextProvider.UIThreadSyncContext.Post((state) =>
                                {
                                    this.BashItems = bashCollection;
                                }, null);

    5. Если нужно делать await в UI потоке, то так пишем:

     SynchronizationContextProvider.UIThreadSyncContext.Post(async (state) =>
                                {
                                    await Task.Delay(10);
                                }, null);
    6. Этот код можно юзать и в ВП проектах, только вместо метода Post, вызываем метод Send.

    • Помечено в качестве ответа evgeniy.polonskiy 3 февраля 2014 г. 11:36
    3 февраля 2014 г. 11:05