-1

I have a property returns the selected item of a radio box group.

public string P1 { get => CB.SelectedItem as string; }

And it's used in an async function which is called in the constructor of a class.

async Task F() {
    var b = P1?.Equals(".........", StringComparison.InvariantCultureIgnoreCase) ?? false;
    var t1 = callAsync().ContinueWith(x => {
        if (b) { .../* use x*/... }
    });
    await t2;
    await t1; //...

The code works fine. However, b is used in many places so I create a property for it and removed the local variable var b = P1?Equals(.....

bool b => P1?.Equals(".........", StringComparison.InvariantCultureIgnoreCase) ?? false;

async Task F() {
    var t1 = callAsync().ContinueWith(x => {
        if (b) { .../* use x*/... } // Exception if F() is called in constructor
    });
    await t2;
    await t1; //...

Now it got the following error when access CB.SelectedItem?

Cross-thread operation not valid: Control 'CB' accessed from a thread other than the thread it was created on.

Update: I found all the code works if not called from a constructor.

ca9163d9
  • 27,283
  • 64
  • 210
  • 413
  • Quite explicit error message. `BeginInvoke()` to access the Control's properties. – Jimi Mar 07 '19 at 21:51
  • Nothing to do with the property assignment. You must be accessing it from a different thread – the.Doc Mar 07 '19 at 21:52
  • @the.Doc, the call is in the `ContinueWith(`, I've updated the question. – ca9163d9 Mar 07 '19 at 21:59
  • Don't use `ContinueWith`, ever. Use `await` to add continuations. Among other things, which you also want to do the way `await` does it, it ensures continuations run in the current synchronization context. – Servy Mar 07 '19 at 22:06
  • @Servy, I already used `await`. (The question is updated.) I put some code in `ContinueWith` because these codes need to be run as soon as possible. (not be blocked by the mulitple await) – ca9163d9 Mar 07 '19 at 22:13
  • @ca9163d9 You can still do the same thing using `await` to add such a continuation. In the case of the code shown though, it simply means adding the code in the continuation after `await t1`. Depending on what you want to do you may need to *use* `await` differently, but you still want to use it to add all of your continuations. It literally is just adding a continuation, so there's never a situation where you *couldn't* use `await` instead of `ContinueWith`. – Servy Mar 07 '19 at 22:17
  • The body of `ContinueWith` will use `x`. I think I will not be able to get the value of `x` until `await t1;`? And I need to await t2 first. (Question updated.) – ca9163d9 Mar 07 '19 at 22:23
  • The last part of the title of the question does not makes sense, I encourage you to read it carefully and see if it makes sense for you. Besides that, it is a very good question. – meJustAndrew Mar 07 '19 at 22:24
  • @meJustAndrew, the title is updated. Still a little bit messy – ca9163d9 Mar 07 '19 at 22:27
  • @ca9163d9 Then *don't* await `t2` first, since you want to do that behavior before `t2` has finished, but after `t1` has finished. – Servy Mar 07 '19 at 22:32
  • @Servy, `t2` has similar codes (as `t1`) which need to be done asap. – ca9163d9 Mar 07 '19 at 22:35
  • @ca9163d9 That doesn't affect your ability to await `t1` before `t2`. It doesn't change when `t2` finishes, just whether you wait for it to finish before doing that logic. – Servy Mar 07 '19 at 23:02
  • @Servy, the details can be found in this question https://stackoverflow.com/questions/54833887/consume-the-result-of-async-calls-with-different-return-type-as-soon-as-any-on?noredirect=1#comment96473749_54833887 – ca9163d9 Mar 07 '19 at 23:07
  • @ca9163d9 You literally just linked to an example of how to solve this problem using `await`. You accepted an extremely poor answer to that question that should never be used, and the problem you're encountering here is just one of the many reasons why. You already were presented with a working solution. You only need to use it (and if you want to be courteous, remove the acceptance of such a low quality answer, so as to not mislead future readers). – Servy Mar 07 '19 at 23:11
  • @Servy, is the second answer, which creates a set of new functions, of the question better? – ca9163d9 Mar 07 '19 at 23:15
  • 1
    if in the first line of F() you add var a = b; then in ContinueWith change if(b) to if(a), then the actual call to CB.SelectedItem will be invoked outside ContinueWith – KMoussa Mar 07 '19 at 23:33
  • @KMoussa, you nailed it. It works now! But why? – ca9163d9 Mar 07 '19 at 23:42
  • @ca9163d9 glad it worked, I added an answer with a bit more description, hope it clarifies the idea – KMoussa Mar 07 '19 at 23:46

1 Answers1

0

The difference is that when b is a property, its getter is invoked inside ContinueWith, and that getter accesses CB.SelectedItem

If, instead, you execute the getter inside F(), then use the result in ContinueWith, it should be functionally identical to your first example

async Task F() {
    var a = b; //invokes CB.SelectedItem in the UI thread
    var t1 = callAsync().ContinueWith(x => {
        if (a) { /*... }
    });
KMoussa
  • 1,568
  • 7
  • 11
  • BTW, I found that the second one works if it's not called in a constructor. What could be the reason? – ca9163d9 Mar 08 '19 at 04:48
  • hard to tell without seeing the code, but the fact that it's in a constructor shouldn't have a direct effect. Maybe some other condition if(b) not to be reached or something? – KMoussa Mar 08 '19 at 07:26