C# VSTO plugin TAP keert niet terug naar UI-thread

Pagina: 1
Acties:

Onderwerpen

Vraag


Acties:
  • 0 Henk 'm!

  • Lustucru
  • Registratie: Januari 2004
  • Niet online

Lustucru

26 03 2016

Topicstarter
Bij de ontwikkeling van een outlookplugin loop ik tegen het probleem aan dat een async button event handler na een await niet verder gaat op de UI-thread:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 private async void cmdDoit_ClickAsync(object sender, EventArgs e)
        {
           //wat voorbereiding waaronder aanpassen van de stausbalk
            this.statusText.Text = "Agenda openen... moment geduld aub";
            specification s = new specification();
            s.CollectAsync();
            await s.CollectAsync(); 
            //opruimen en diverse vervolgstappen in de ui
            this.statusText.Text = "Gereed";
        }

 class specification
    {
        public async Task CollectAsync()
        {
            //timeconsuming io work
            await Task.Delay(5000);
        }
    }

Als ik een en ander toepas in een normale winforms app is er geen vuiltje aan de lucht, maar als vsto plugin knalt hij eruit bij het resetten van de statustext, omdat de control niet benaderd wordt vanuit de juiste thread. Volgens de documentatie van Microsoft zou de suspended methode na een await moeten doorgaan op de oorspronkelijke thread, maar blijkbaar gaat dat niet op voor een outlook plugin. Uiteraard zou ik de rest van de eventhandler apart kunnen zetten met een invoke() ervoor, maar dat voelt als een noodverband. Wat gebeurt hier en hoe is dat het beste op te lossen?

Relevante software en hardware die ik gebruik
Visual Studio community, .net 4.7.

Wat ik al gevonden of geprobeerd heb
Eindeloos MSDN gelezen, maar daar word ik voor dit specifieke geval niet veel wijzer van. :'( en lopen prutsen met synchronisation contexts, zonder soelaas.

De oever waar we niet zijn noemen wij de overkant / Die wordt dan deze kant zodra we daar zijn aangeland

Beste antwoord (via Lustucru op 15-12-2020 13:51)


  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
@Lustucru het lijkt een bug in de implementatie van SynchronisationContext te zijn
On a thread without explicit WinForms message loop (i.e., any thread which hasn't entered Application.Run or Form.ShowDialog), calling Application.DoEvents will replace the current synchronization context with the default SynchronizationContext, provided WindowsFormsSynchronizationContext.AutoInstall is true (which is so by default).
Voorgestelde oplossing is om deze code toe te voegen
C#:
1
2
3
4
5
6
7
8
9
10
11
struct SyncContextSetup
{
    public SyncContextSetup(bool autoInstall)
    {
        WindowsFormsSynchronizationContext.AutoInstall = autoInstall;
        SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
    }
}

static readonly SyncContextSetup _syncContextSetup =
    new SyncContextSetup(autoInstall: false);

Die installeert door een static initializatie een SynchronizationContext en zet AutoInstall op false. Ik weet niet 100% zeker of het ook het issue is waar jij tegenaan loopt, maar het lijkt mij niet onwaarschijnlijk aangezien Office een non-managed process is die de managed VSTO plugin laad.

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”

Alle reacties


Acties:
  • 0 Henk 'm!

  • beany
  • Registratie: Juni 2001
  • Laatst online: 06:51

beany

Meeheheheheh

code:
1
private async void cmdDoit_ClickAsync(object sender, EventArgs e)


Begin er mee om deze methode als result ook een Task te geven. Dus:

code:
1
private async Task cmdDoit_ClickAsync(object sender, EventArgs e)

Dagelijkse stats bronnen: https://x.com/GeneralStaffUA en https://www.facebook.com/GeneralStaff.ua


Acties:
  • 0 Henk 'm!

  • Lustucru
  • Registratie: Januari 2004
  • Niet online

Lustucru

26 03 2016

Topicstarter
beany schreef op maandag 14 december 2020 @ 15:18:
code:
1
private async void cmdDoit_ClickAsync(object sender, EventArgs e)


Begin er mee om deze methode als result ook een Task te geven. Dus:

code:
1
private async Task cmdDoit_ClickAsync(object sender, EventArgs e)
Niet volgens MSDN: eventhandlers zijn fire en forget en de uitzondering op de regel dat een async methode een task moet resulteren. Maar niet geschoten altijd mis, dus ik ga het eens proberen.

edit:

Compiler says no ;) Eventhandlers moeten void hebben als returntype.

[ Voor 7% gewijzigd door Lustucru op 14-12-2020 15:24 ]

De oever waar we niet zijn noemen wij de overkant / Die wordt dan deze kant zodra we daar zijn aangeland


Acties:
  • 0 Henk 'm!

  • beany
  • Registratie: Juni 2001
  • Laatst online: 06:51

beany

Meeheheheheh

Lustucru schreef op maandag 14 december 2020 @ 15:19:
[...]


Niet volgens MSDN: eventhandlers zijn fire en forget en de uitzondering op de regel dat een async methode een task moet resulteren. Maar niet geschoten altijd mis, dus ik ga het eens proberen.
Eh, ow :X Sorry, ik doe nooit iets met UI thread (zit meer in back-ends te pielen :P ). Dus mijn methods zijn altijd 'async Task'.

En als je de labels onder de Dispatcher update?

code:
1
Dispatcher.Invoke(new Action(() => this.statusText = "bla bla"));

Dagelijkse stats bronnen: https://x.com/GeneralStaffUA en https://www.facebook.com/GeneralStaff.ua


Acties:
  • 0 Henk 'm!

  • Lustucru
  • Registratie: Januari 2004
  • Niet online

Lustucru

26 03 2016

Topicstarter
Invoke werkt, -uiteraard-, maar ik snap niet dat het nodig is: liever zou ik het standaardgedrag afdwingen dat de suspended methode keurig verder gaat op dezelfde thread als waar hij was toen hij werd suspended.

C#:
1
2
3
4
5
6
7
8
            if (this.InvokeRequired)
            {
                this.Invoke(new System.Action(() =>
              {
                 //and more
                  this.statusText.Text = "Gereed";
              }));
            }

De oever waar we niet zijn noemen wij de overkant / Die wordt dan deze kant zodra we daar zijn aangeland


Acties:
  • 0 Henk 'm!

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Ik vermoed dat een VSTO plugin een andere SynchronisationContext heeft ( https://docs.microsoft.co...text.current?view=net-5.0 )

Dat is ieder geval het onderdeel dat ervoor zorgt dat je op dezelfde thread terugkomt in winforms, hoe dat binnen de VSTO context zou moeten gebeuren weet ik niet.

Mogelijk kun je hier wat mee: https://stackoverflow.com...-hosted-by-an-unmanaged-a

[ Voor 18% gewijzigd door Woy op 14-12-2020 15:58 ]

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


Acties:
  • 0 Henk 'm!

  • Lustucru
  • Registratie: Januari 2004
  • Niet online

Lustucru

26 03 2016

Topicstarter
hmm, geen post om even tot me te nemen. Zo diep zit ik niet in threadmodellen en synchronization-context als vrijetijds-maak-af-en-toe-een tooltje codeplakker. Maar zonder meer interessant leesvoer. :)

De oever waar we niet zijn noemen wij de overkant / Die wordt dan deze kant zodra we daar zijn aangeland


Acties:
  • Beste antwoord
  • 0 Henk 'm!

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
@Lustucru het lijkt een bug in de implementatie van SynchronisationContext te zijn
On a thread without explicit WinForms message loop (i.e., any thread which hasn't entered Application.Run or Form.ShowDialog), calling Application.DoEvents will replace the current synchronization context with the default SynchronizationContext, provided WindowsFormsSynchronizationContext.AutoInstall is true (which is so by default).
Voorgestelde oplossing is om deze code toe te voegen
C#:
1
2
3
4
5
6
7
8
9
10
11
struct SyncContextSetup
{
    public SyncContextSetup(bool autoInstall)
    {
        WindowsFormsSynchronizationContext.AutoInstall = autoInstall;
        SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
    }
}

static readonly SyncContextSetup _syncContextSetup =
    new SyncContextSetup(autoInstall: false);

Die installeert door een static initializatie een SynchronizationContext en zet AutoInstall op false. Ik weet niet 100% zeker of het ook het issue is waar jij tegenaan loopt, maar het lijkt mij niet onwaarschijnlijk aangezien Office een non-managed process is die de managed VSTO plugin laad.

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


Acties:
  • 0 Henk 'm!

  • Lustucru
  • Registratie: Januari 2004
  • Niet online

Lustucru

26 03 2016

Topicstarter
Woy schreef op maandag 14 december 2020 @ 17:11:
Voorgestelde oplossing is om deze code toe te voegen
C#:
1
2
3
4
5
6
7
8
9
10
11
struct SyncContextSetup
{
    public SyncContextSetup(bool autoInstall)
    {
        WindowsFormsSynchronizationContext.AutoInstall = autoInstall;
        SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
    }
}

static readonly SyncContextSetup _syncContextSetup =
    new SyncContextSetup(autoInstall: false);
Thx, en klinkt logisch, maar dan even een dom vraagje: wáár voeg ik die toe? 8)

De oever waar we niet zijn noemen wij de overkant / Die wordt dan deze kant zodra we daar zijn aangeland


Acties:
  • 0 Henk 'm!

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
@Lustucru
Ik weet niet exact de structurering van een VSTO project, maar ik zou het toevoegen aan je main entry point. Op zich haalt het niet zo heel veel uit zolang je zeker weet dat minstens de static initializer van het type die de SyncContextSetup static member heeft wordt uitgevoerd. Bij je main entry point class weet je dat ieder geval zeker. In het geval van een console applicatie bijvoorbeeld:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Program
{
    struct SyncContextSetup
    {
        public SyncContextSetup(bool autoInstall)
        {
            WindowsFormsSynchronizationContext.AutoInstall = autoInstall;
            SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
        }
    }

    static readonly SyncContextSetup _syncContextSetup = new SyncContextSetup(autoInstall: false);

    static void Main(string[] args)
    {
    }
}

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


Acties:
  • 0 Henk 'm!

  • Lustucru
  • Registratie: Januari 2004
  • Niet online

Lustucru

26 03 2016

Topicstarter
Woy schreef op maandag 14 december 2020 @ 17:11:
Ik weet niet 100% zeker of het ook het issue is waar jij tegenaan loopt, maar het lijkt mij niet onwaarschijnlijk aangezien Office een non-managed process is die de managed VSTO plugin laad.
Helaas... toegevoegd aan het form waarbinnen de eventhandler wordt aangeroepen maar nog altijd keert de supsended eventhandler na de await niet terug op de UI-thread. Je verklaring klinkt logisch, maar als oplossing zal ik het voorlopig dus op invokerequired?invoke() moeten houden. :'(

De oever waar we niet zijn noemen wij de overkant / Die wordt dan deze kant zodra we daar zijn aangeland


Acties:
  • 0 Henk 'm!

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
Lustucru schreef op dinsdag 15 december 2020 @ 13:50:
[...]

Helaas... toegevoegd aan het form waarbinnen de eventhandler wordt aangeroepen maar nog altijd keert de supsended eventhandler na de await niet terug op de UI-thread. Je verklaring klinkt logisch, maar als oplossing zal ik het voorlopig dus op invokerequired?invoke() moeten houden. :'(
Ok, dan vermoed ik toch net een iets ander (Vergelijkbaar) probleem. Log eens in je code of SynchonisationContext.Current NULL is of niet.

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


Acties:
  • 0 Henk 'm!

  • Lustucru
  • Registratie: Januari 2004
  • Niet online

Lustucru

26 03 2016

Topicstarter
hmm interessant. Hij start dus in de syncronisationcontext 'outlook', maar gaat verder zonder synchronisationcontext. Ik voel me als een garnaal in de buizen van het CERN.

code:
1
2
3
4
5
Before starting task: 
System.Threading.SynchronizationContext 'outlook.exe' (CLR v4.0.30319: OLDeclAssist.vsto|vstolocal): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\mscorlib.resources\v4.0_4.0.0.0_nl_b77a5c561934e089\mscorlib.resources.dll'. Module was built without symbols.

After await:
NULL


Ik vermoed dus dat ik bij het aanroepen van de task moet aangeven met welke synchronisation context hij moet resumen, maar daar kwam ik niet uit de correcte syntax.

[ Voor 14% gewijzigd door Lustucru op 15-12-2020 14:44 ]

De oever waar we niet zijn noemen wij de overkant / Die wordt dan deze kant zodra we daar zijn aangeland


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Lustucru schreef op dinsdag 15 december 2020 @ 14:42:
System.Threading.SynchronizationContext
Maar dat is toch precies wat je verwacht bij die context?
The SynchronizationContext class is a base class that provides a free-threaded context with no synchronization.
Dus je komt na een await terug in de thread pool, die heeft geen SynchronizationContext, dus daarna is die null. https://stackoverflow.com...ared-across-the-whole-app doet mij denken dat je hem wel zelf goed in kan stellen binnen een outlook-plugin. SynchronizationContext.SetSynchronizationContext wordt niet aangeroepen of op de verkeerde thread/plek (of er is code die deze verkeerd reset).

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • Lustucru
  • Registratie: Januari 2004
  • Niet online

Lustucru

26 03 2016

Topicstarter
Ik geef het op... me nog ingelezen in synchronisation contexts, geset, uitgelezen,maar wat ik ook probeer: hij blijft terugkomen op de verkeerde thread.Ook configureawait: geen resultaat. Ik zou nog iets met de dispatcher kunnen proberen, maar voor nu hou ik het maar op invoke(). Al blijft het gevoel dat ik het niet snap behoorlijk irritant. :(

De oever waar we niet zijn noemen wij de overkant / Die wordt dan deze kant zodra we daar zijn aangeland


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Ik neem aan dat je https://devblogs.microsoft.com/dotnet/configureawait-faq/ hebt gelezen? Mijn vermoeden is dat je de context ergens in de juiste thread moet zetten bij een onload oid (wellicht na WindowsFormsSynchronizationContext.AutoInstall = false), en dat je dan in cmdDoit_ClickAsync kan controleren of je inderdaad een WindowsFormsSynchronizationContext hebt. Als dat lukt maar er wordt niets uitgevoerd dan moet je op de een of andere manier zorgen dat er een controlToSendTo is, of je moet je eigen context schrijven. ConfigureAwait staat al goed op true.

En Invoke werkt natuurlijk ook prima, WindowsFormsSynchronizationContext gebruikt ook gewoon Invoke.

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten

Pagina: 1