0

Simplifited solution graph. When I'm trying to add ListViewItem to Browser it throws System.InvalidOperationException: caller can't access object because it belongs to another thread.

//[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddItems(string ID)
{
    Browser.Dispatcher.Invoke(() =>
    {
        IEnumerable<ListViewItem> Items = Helper.GetCached(ID);

        lock (((ICollection)Items).SyncRoot)
        {
            if (Items != null)
            {
                foreach (var Item in Items)
                {
                    Browser.Items.Add(Item);//System.InvalidOperationException
                }
            }
        }

    });
}

Helper implementation:

internal static class Helper
{
    static object @lock = new object();

    static readonly Dictionary<string, List<ListViewItem>> Cached = new Dictionary<string, List<ListViewItem>>();
    static readonly List<HintFilter> HintFilters = new List<HintFilter>();

    internal static void AddHintFilterService(HintFilter Filter)
    {
        lock (((ICollection)HintFilters).SyncRoot)
        {
            if (!HintFilters.Contains(Filter))
                HintFilters.Add(Filter);
        }
    }

    internal static void ClearHintFilters()
    {
        lock (((ICollection)HintFilters).SyncRoot)
        {
            HintFilters.Clear();
        }
    }


    #region Services
    static readonly List<IService> Services = new List<IHintParserService>();

    internal static void ServicesParserEntry()
    {
        System.Timers.Timer ServicesUpdate = new System.Timers.Timer(1000)
        {
            AutoReset = true,
            Enabled = true
        };

        ServicesUpdate.Elapsed += ServicesUpdate_Elapsed;
        ServicesUpdate.Start();
    }

    static void ServicesUpdate_Elapsed(object sender, ElapsedEventArgs e)
    {
        lock (((ICollection)Services).SyncRoot)
        {
            if (!Services.Any())
                return;

            foreach (var Service in Services)
            {
                if (Service.NeedReparse())
                    ThreadPool.QueueUserWorkItem(Parse, Service);
            }
        }

    }

    internal static List<ListViewItem> GetCached(string ID)
    {
        lock (((IDictionary)Cached).SyncRoot)
        {
            if (Cached.Keys.Contains(ID))
                return Cached[ID];
            else
                return null;
        }
    }

    internal static void ClearCached()
    {
        lock (((IDictionary)Cached).SyncRoot)
        {
            Cached.Clear();
        }
    }

    internal static void AddService(IService Service)
    {
        lock (((ICollection)Services).SyncRoot)
        {
            if (!Services.Contains(Service))
                Services.Add(Service);
        }
    }

    internal static void RemoveService(IService Service)
    {
        lock (((ICollection)Services).SyncRoot)
        {
            if (!Services.Contains(Service))
                throw new InvalidOperationException("Unable to remove service:" + Service.ID());


            Services.Remove(Service);
        }
    }

    static object ParseLock = new object();
    static void Parse(object O)
    {
        lock (ParseLock)
        {
            var Service = O as IService;

            if (!Service.NeedReparse())
                return;

            lock (((IDictionary)Cached).SyncRoot)
            {
                if (Cached.Keys.Contains(Service.ID()))
                    Cached.Remove(Service.ID());//clear
            }

            var Hints = Service.Parse();

            Cache(Hints, Service.ID());
        }
    }

    static void Cache(List<Hint> Hints, string ID)
    {
        Thread CacheThread = new Thread(() =>
        {
            //Thread.CurrentThread.SetApartmentState(ApartmentState.STA);

            List<ListViewItem> CachedHints = new List<ListViewItem>();

            foreach (Hint H in Hints)
            {
                CachedHints.Add(Cache(H));
            }

            lock (((IDictionary)Cached).SyncRoot)
            {
                Cached.Add(ID, CachedHints);
            }
        });

        CacheThread.SetApartmentState(ApartmentState.STA);//MTA can't create UI elements
        CacheThread.Start();
        CacheThread.Join();
    }

    internal static ListViewItem Cache(Hint Hint)
    {
        ListViewItem Item = new ListViewItem()
        {
            MaxHeight = 40,
            MinHeight = 0,
            Margin = new Thickness(0)
        };
        WrapPanel Wrapper = new WrapPanel();

        BitmapImage FilterContent = null;

        if (!string.IsNullOrEmpty(Hint.GroupName))
        {
            var HintFilter = HintFilters.FirstOrDefault(f => f.GroupName == Hint.GroupName);

            if (HintFilter != null)
            {
                FilterContent = HintFilter.Image;
            }
        }

        if (FilterContent != null)
        {
            var Img = new Image()
            {
                Width = 30,
                Height = 24,
                Source = FilterContent
            };
            Wrapper.Children.Add(Img);
        }

        TextBlock Text = new TextBlock()
        {
            Text = Hint.Display,
            VerticalAlignment = VerticalAlignment.Bottom,
            Margin = new Thickness(0),
            Height = 24
        };

        Wrapper.Children.Add(Text);
        Wrapper.Margin = new Thickness(0);
        Wrapper.MinHeight = 24;
        Item.Content = Wrapper;

        return Item;
    }

    #endregion
}

My question is: how to do it correctly referencing graph? Mabey I should try to make copy of elements and use them for Browser. The solution isn't small, so this will be hard to provide all details.

Call stack:

  • [Plugin] Request elements update

  • [PluginWrapperMethod]

  • [CurrentMethod]

  • Specify what classes Browser and ListViewItem are. – H H Sep 29 '17 at 17:48
  • The standard answer here is to use Dispatcher.Invoke(). Since you already do that there is something here that we can't see. Don't hold your breath for an answer. – H H Sep 29 '17 at 17:49
  • https://stackoverflow.com/questions/4815414/how-do-i-update-a-wpf-window-with-information-from-a-background-thread?rq=1 – Thomas Weller Sep 29 '17 at 22:06
  • https://stackoverflow.com/questions/23397496/c-sharp-wpf-updating-the-ui-from-another-thread-created-within-another-class?rq=1 – Thomas Weller Sep 29 '17 at 22:07
  • https://stackoverflow.com/questions/33680398/c-sharp-wpf-how-to-simply-update-ui-from-another-class-thread?rq=1 – Thomas Weller Sep 29 '17 at 22:07
  • When I google for the title of your question, there are 8 answers on this site popping up. – Thomas Weller Sep 29 '17 at 22:08
  • @Thomas Weller I saw them, but they just don't work. – Matsu Horigome Sep 30 '17 at 09:00
  • @HenkHolterman Browser is ListView and ListViewItem is default System.Windows.Controls.ListViewItem. – Matsu Horigome Sep 30 '17 at 09:05
  • Changing Browser.Dispatcher to Application.Dispatcher may help. Also, add a breakpoint inside Browser.Dispatcher.Invoke() and compare ThreadId there to the ThreadId collected from a thread you know is a UI thread. They must be the same. – Maciek Świszczowski Sep 30 '17 at 10:37

0 Answers0