79502751

Date: 2025-03-12 06:11:55
Score: 0.5
Natty:
Report link

First I want to know if the general idea is somewhat acceptable, or if there is a much better solution.

The general idea is OK. To my knowledge there is no outstandingly better solution. There is no built-in mechanism available that does exactly what you want, so you'll have to write some custom code either way. Your approach has the advantage of not allocating resources when there is no work to do, and the disadvantage that a new Thread has to be spawned each time a new request for work appears after an idle period. Other approaches, like using a BlockingCollection<T> or a Channel<T>, might have the opposite advantages and disadvantages.

What should I need to do to make this thread-safe?

You must enclose all interactions with all of your shared state inside lock blocks, and use the same locker object in all lock blocks. The shared state in your case is the internList and internThread fields. Since the internList is readonly, it could also serve a dual purpose as the locker object for the lock blocks. Inside the lock blocks you should do nothing else than interact with the shared state. Any unrelated work should stay outside, so that the lock can be released as soon as possible. The more you hold the lock, the higher the chances that another thread will be blocked, causing contention and slowing down your application.

Making your code thread-safe is an exercise in discipline, not in ingenuity. Every - single - interaction with your shared state must be protected, no exceptions. Even reading the Count of the List<T> must be protected. Only one thread at a time should be allowed to interact with your shared state. The moment that you decide to make an exception is the moment that your code becomes incorrect, and its behavior undefined.

What should I do to have the Address_Loaded event run in the UI thread?

There are many ways, with the most primitive being the Control.Invoke/Control.BeginInvoke APIs. My suggestion is to use the SynchronizationContext abstraction. Add a readonly SynchronizationContext field in your DatabaseQueue class, initialize it to SynchronizationContext.Current in the constructor of your class, and then use it in the ProcessQueueAsync method like this:

_syncContext.Send(_ =>
{
    // Here we are on the context where the DatabaseQueue was instantiated,
    // which should be the WinForms UI thread.
    currentAddress.IsLoaded = true;
}, null);

You could consider enclosing all Address modifications inside the delegate, so that each Address is mutated exclusively on the UI thread:

_syncContext.Send(_ =>
{
    currentAddress.PropertyX = valueX;
    currentAddress.PropertyY = valueY;
    currentAddress.IsLoaded = true;
}, null);

Regarding the choice between _syncContext.Send and _syncContext.Post, the first (Send) will block the background thread until the delegate has been fully invoked on the UI thread, including the invocation of all event handlers that are attached to the Address.Loaded event, and the second (Post) allows the background thread to continue working immediately after just scheduling the delegate. It's up to you what behavior you prefer.

Reasons:
  • Blacklisted phrase (0.5): I need
  • Blacklisted phrase (1): I want to know
  • Blacklisted phrase (2): What should I do
  • Whitelisted phrase (-1): in your case
  • RegEx Blacklisted phrase (1): I want
  • Long answer (-1):
  • Has code block (-0.5):
  • Contains question mark (0.5):
  • High reputation (-2):
Posted by: Theodor Zoulias