Diskuze: WinRT - asynchroní metoda a zamrzání UI

C# .NET .NET (C# a Visual Basic) WinRT - asynchroní metoda a zamrzání UI American English version English version

Avatar
Petr Nymsa
Redaktor
Avatar
Petr Nymsa:

Už se obracím na vás. Máte někdo zkušenosti s novým asynchroním programováním přes acync a await. Novinka C# 5.

Jde mi o jedno. Tvořím narychlo (ano narychlo, deadline je nebezpečně blízko) aplikaci pro Windows 8 a jedním důležitým prvkem je nezamrzání UI když se něco děje.

U mě se děje generování ASCII. Z nějakého důvodu mi vlákno s UI zamrzne i když by nemělo ("nemělo"). Podmínkou je aby UI vypadalo že stále běží v pohodě i když se v pozadí něco děje. Stále můžu přejíždět přes tlačítka, která se animují apod. Pošlu ústřižky kódu

Zde volám metodu která generuje ASCII -> je zavolána asynchronně. Po jejím dokončení nastavím nový text.

string result = await manager.GenerateAsciiText(file);
manager.HTML = result;

Vlastní metoda pro generování. Kód je s prominutím prasárna. Generuje to špatně, každopádně ve WinForm to i celkem běhá. Až vyřeším UI přepíšu to.

public async Task<string> GenerateAsciiText(StorageFile file)
      {
          ascii = new StringBuilder();

          using(IRandomAccessStream stream =await file.OpenAsync(FileAccessMode.Read))
          {
              wb = await new WriteableBitmap(1, 1).FromStream(stream);
          }
          for (int h = 0; h < wb.PixelHeight - Size; h += Size)
          {
              for (int w = 0; w < wb.PixelWidth - Size; w += Size)
              {

                  int color = 0;
                  for (int x = 0; x < Size; x++)
                  {
                      for (int y = 0; y < Size; y++)
                      {
                          Color pixelColor = wb.GetPixel(w + y, h + x);
                          color += (pixelColor.R + pixelColor.G + pixelColor.B) / 3;
                      }
                  }

                  color = color / (Size * Size);
                  Color grayColor = Color.FromArgb(255, (byte)color, (byte)color, (byte)color);

                  ascii.Append(getAsciiChar(grayColor.A));


              }
              ascii.Append(Environment.NewLine);
          }

          return ascii.ToString();
      }

StorageFile = soubor s obrázkem
IRandomAccessStre­am = rohraní pro čtená datového toku
WriteableBitmap - bitmapa která jde upravovat. Používám cizí knihovnu která doplnuje metodu GetPixel, SetPixel,...

Vím že je to i celkem pomalý, ale to není důvod aby to zamrzlo při obrázku 150px * 150px.

Vlákno se má oddělit a UI má běžet stále stejně.

Nějaké info http://blog.imp.cz/…-jazyka-C-50

http://vimeo.com/57608362 - video - ukázka synchroního a asynchroního chování.

Děkuju za rady ! :) A klidně mě umlaťte :D.

Odpovědět 28.5.2013 19:39
Pokrok nezastavíš, neusni a jdi s ním vpřed
Avatar
Petr Nymsa
Redaktor
Avatar
Petr Nymsa:

Dokázal by někdo poradit ?

Nahoru Odpovědět 29.5.2013 11:08
Pokrok nezastavíš, neusni a jdi s ním vpřed
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Petr Nymsa
David Čápka:

Tyto nové metody neznám, ale určitě je v WPFku něco jako ve WinForms Beckground worker, nebo snad ne?

Nahoru Odpovědět 29.5.2013 12:28
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na Petr Nymsa
David Čápka:

Teď koukám, že ho tam můžeš normálně použít, pokud chceš dělat jednu úlohu na pozadí, tak se s nějkými asynchronními metodami vůbec nezatěžuj, existují wrappery, co tě od toho odstíní.

Nahoru Odpovědět 29.5.2013 12:32
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
Petr Nymsa
Redaktor
Avatar
Odpovídá na David Čápka
Petr Nymsa:

To je ten problém. UI prostě zamrzne. A aplikaci potom neuznají. Nevím, v téhle metodě je někde chybě.

Nyní jsem našel zpsůbo jak jednodušše načíst pixely -> byte array.

S tvorbou async metod přes await jsem neměl nikdy problém. V pohodě jsem měl metodu která generovala prvočísla do vysoké hodnoty, přes await a UI nezamrzlo.

Ještě se ozvu jak jsem to vyřešil, nyní už možná mám řešení.

Nahoru Odpovědět 29.5.2013 13:33
Pokrok nezastavíš, neusni a jdi s ním vpřed
Avatar
Michal Žůrek (misaz):

Problém bude v té cizí knihovně.

Nahoru Odpovědět 29.5.2013 14:29
Nesnáším {}, proto se jim vyhýbám.
Avatar
exyi
Redaktor
Avatar
exyi:

vim ze v tomto pripade je to blbe, ale myslim ze by to slo pres

Task.Run(()=> {
    //...
});
 
Nahoru Odpovědět 29.5.2013 14:44
Avatar
Odpovídá na exyi
Michal Žůrek (misaz):

Vítej ve světě windows 8. Možná by to šlo a lidi z microsoftu používají (a na konferencích učí) Async a Await. Asi to nedělají ze srandy králikům. :)

Nahoru Odpovědět 29.5.2013 15:00
Nesnáším {}, proto se jim vyhýbám.
Avatar
Petr Nymsa
Redaktor
Avatar
Odpovídá na exyi
Petr Nymsa:

Posílám update kód. Opavdu už nevím co a jak ;(. Pro update UI se musí využít Dispatcher. Je divné jedno - když ho nevolám a neupdatuju UI! UI zamrzne. Když ho Updatuju UI nezamrzá.

Metodu, která generuje volám takto

await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
                     {
                          manager.GenerateAsciiHtml(bmp, pixels);
                     });

Metoda která generuje. GetPixel už je moje metoda.

public void GenerateAsciiHtml(BitmapImage bmp, byte[] pixels)
       {
           ascii = new StringBuilder();
           ascii.Append(string.Format("<body style=\"font-family: 'Courier New', Courier, monospace;font-size: {0}px;\">", 5));
           for (int h = 0; h < bmp.PixelHeight - Size; h += Size)
           {
               for (int w = 0; w < bmp.PixelWidth - Size; w += Size)
               {
                   Color color = bmp.GetPixel(pixels, w, h);
                   ascii.Append(string.Format("<span style='color:rgb({0},{1},{2});'>{3}{3}</span>",
                      color.R,color.G,color.B, getAsciiChar(color.A)));

               }
               ascii.Append("<br>");
           }
           ascii.Append("</body>");
       }

Metoda naschvál nic nevrací. V tomto případě UI nemrzne. Teď ovšem potřebuju jedno. Buď metoda nastaví text pro výpis přímo v sobě nebo vrátí string. Nastíním durhou situaci. Volání metody změním na

string text = "";
                    await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
                        {
                            text =  manager.GenerateAsciiHtml(bmp, pixels);

                        });

do stringu text se přiřadí vrácený vygenerovaný HTML. Ovšem potřebuju udělat jedno. Binduju properties z manageru, tedy ze stejné třídy která generuje a má položku HTML. Jakimle se pokusím text přiřadit do HTML, UI zamrzne.

Dispatcher má v sobě metodu AsAsync() a ta má v sobě ContinueWith(), což by se mělo zavolat až po dokončení.

Mám v tom huláš a už nevím co a jak. Když udělám async i metodu ne generování dostávám error o tom že zasahuju do jiného vlákna.

Prosím nějaká rada, prostě kudy co a jak ? Děkuji moc ! :)

Nahoru Odpovědět 29.5.2013 15:00
Pokrok nezastavíš, neusni a jdi s ním vpřed
Avatar
Petr Nymsa
Redaktor
Avatar
Odpovídá na exyi
Petr Nymsa:

Ano to jsem zkoušel ale tohle mi prostě nefunguje. Zkoušel jsem i generovat číslo, vypočítavat z nich násobky + odmocniny a UI nezamrzlo. Asi je problém že mám tu metodu na generování a zároveň properties pro UI v jedné třídě.

http://vimeo.com/57608362 Zde podle nich i něco zkouším. Už fakt nevím jak :D.

KDyž načítám soubor, napíšu await a vše funguje. Ale tohle mi nestačí, nevím jak to funguje přesně pod pokličkou :/

Nahoru Odpovědět 29.5.2013 15:03
Pokrok nezastavíš, neusni a jdi s ním vpřed
Avatar
exyi
Redaktor
Avatar
Odpovídá na Petr Nymsa
exyi:

to si nemyslim, ze to je tou jednou tridou. A funguje to kdyz pocitas ty cisla a pak neco vratis?

Editováno 29.5.2013 16:58
 
Nahoru Odpovědět 29.5.2013 16:57
Avatar
Petr Nymsa
Redaktor
Avatar
Odpovídá na exyi
Petr Nymsa:

Ano, počítám a vracím double. Schvále jsem měl i tu samotnou metodu async a každé nově vypočtené jsem připsal do souboru opět přes await (jinak to nejde). Vše fungovalo, UI nezamrzlo

Nahoru Odpovědět 29.5.2013 17:11
Pokrok nezastavíš, neusni a jdi s ním vpřed
Avatar
Petr Nymsa
Redaktor
Avatar
Petr Nymsa:

Asi už musím vypadat jako totální blbec. ale já opravdu už nevím jak ;( Už jsem ve stavu kdy bych to nejradši všechno vyhodil oknem :D.

Shrnu co tedy potřebuju a co mám.

Třída DataManager: Drží v sobě data pro Binding, je to čistě Model. Dříve jsem tam měl i metodu pro vygenerování HTML ASCII z obrázku. Přesunul jsem ji.

Třída PhotoAsciiWorker: Obsahuje metodu pro vygenerování HTML ASCII.

Ze třídy MainPage.xaml.cs -> třída která má přístup k View, zavolám metodu GenerateAScii z PhotoASciiWorker. Metoda by měla být asynchronně. Tedy aby UI nezamrzlo a napsalo třeba Working...

Co a jak nastavit ?

Do teď mi vždy stačilo označit metodu async a zavolat ji přes Await. Napíšu "možný" způsob zpracování

textBlock.Text = "Generating...Please wait";

Task<string> task = worker.GenerateAscii(.....) ;
string result = await task;

textBlock.Text = result;
 // ---> UI zamrzne
 // nebo
string reesult = await worker.GenerateAscii(...)

// UI zamrzne

Už fakt nevím co dělat :(

Nahoru Odpovědět 29.5.2013 18:14
Pokrok nezastavíš, neusni a jdi s ním vpřed
Avatar
Odpovídá na Petr Nymsa
Michael Olšavský:

Nechceš se na to vykašlat a použít nové vlákno? :-)

 
Nahoru Odpovědět 29.5.2013 18:15
Avatar
Petr Nymsa
Redaktor
Avatar
Odpovídá na Michael Olšavský
Petr Nymsa:

A jak ? :D S vlákny moc kamarád nejsem. Tohle je navíc nové vlákno, je to akorát nový způsob http://msdn.microsoft.com/…h191443.aspx#… . Chápu jak to funguje ale nechápu proč to nefunguje v tomhle případě. Přitom když si udělám metodu která generuje náodná čísl,a násobí a dělá odmocninu + zapisuje furt do txt, UI nezamrzne

Nahoru Odpovědět 29.5.2013 18:20
Pokrok nezastavíš, neusni a jdi s ním vpřed
Avatar
Petr Nymsa
Redaktor
Avatar
Petr Nymsa:

Tak nyní zkouším už všechno možný. Nevím, prostě nevím. Zde třída PhotoAsciiWorker. Schválně jsem dal nastavení hodnot pro výpočet do jiné metody.

class PhotoAsciiWorker
    {
        byte[] pixels;
        BitmapImage bmp;
        int size;

        public void SetValues(BitmapImage bmp, byte[] pixels, int size)
        {
            this.pixels = pixels;
            this.bmp = bmp;
            this.size = size;
        }

        public string GenerateAsciiHtml()
        {
            StringBuilder ascii = new StringBuilder();
            ascii = new StringBuilder();
            ascii.Append(string.Format("<body style=\"font-family: 'Courier New', Courier, monospace;font-size: {0}px;\">", 5));



            for (int h = 0; h < bmp.PixelHeight - size; h += size)
            {
                for (int w = 0; w < bmp.PixelWidth - size; w += size)
                {
                    Color color = bmp.GetPixel(pixels, w, h);
                    ascii.Append(string.Format("<span style='color:rgb({0},{1},{2});'>{3}{3}</span>",
                       color.R, color.G, color.B, getAsciiChar(color.A)));

                }
                ascii.Append("<br>");
            }
            ascii.Append("</body>");


            return ascii.ToString();
        }

Zde volání metody

manager.HTML = "Working...";
                    worker.SetValues(bmp, pixels, manager.Size);
                    Task<string> task = Task.Run(new Func<string>(worker.GenerateAsciiHtml));

                    string result = await task;

                    manager.HTML = result;

Dostunu tento error:

The application called an interface that was marshalled for a different thread.
Nahoru Odpovědět 29.5.2013 18:29
Pokrok nezastavíš, neusni a jdi s ním vpřed
Avatar
Odpovídá na Petr Nymsa
Michal Žůrek (misaz):

A nemělo by být GenerateAsciiHtml async?

Nahoru Odpovědět 29.5.2013 18:31
Nesnáším {}, proto se jim vyhýbám.
Avatar
Petr Nymsa
Redaktor
Avatar
Odpovídá na Michal Žůrek (misaz)
Petr Nymsa:

Nn, zde ukázka testovacího progámku.

Task<double> task = Task.Run(new Func<double>(Calculate));
           double result = await task;
           txResult.Text = result.ToString();


 public double Calculate()
       {
           double r = 0;

           for (int i = 0; i < 10000 * 500 *200; i++)
           {
               r += Math.Sqrt(i);

               double bl = r * r * r * - Math.Pow(i,2) + Math.Sqrt(r);

           }

           return r;
       }

Vše funguje. UI nezamrzne

Editováno 29.5.2013 18:34
Nahoru Odpovědět 29.5.2013 18:33
Pokrok nezastavíš, neusni a jdi s ním vpřed
Avatar
Odpovídá na Petr Nymsa
Michal Žůrek (misaz):

Tak tak udělej i ty pixely, pokud funguje o, tak musi i pixely.

Nahoru Odpovědět 29.5.2013 18:43
Nesnáším {}, proto se jim vyhýbám.
Avatar
Petr Nymsa
Redaktor
Avatar
Odpovídá na Michal Žůrek (misaz)
Petr Nymsa:

To je to, nefunguje a já už absolutně nemám tušení proš o_O nebo jak mám prostrčit skrz Func i parametry metody které potřebuje ?

Nahoru Odpovědět 29.5.2013 18:45
Pokrok nezastavíš, neusni a jdi s ním vpřed
Avatar
Petr Nymsa
Redaktor
Avatar
Petr Nymsa:

Už toho mám akorát dost. Misaz mi poslal video kde ukázali jak dělat složité operace v jiném Threadu než je Thread UI -> UI nezamrzne. Kód může vypadat takto.

  private async void btnAsync_Click(object sender, RoutedEventArgs e)
        {
             // vytvoří nové vlákno, čeká na jeho dokončení. "Vyskočím" z //metody pryč. UI je aktivní dál
            long vysledek = await Task.Run<long>(() => Calculate(1000000000000));
 /// dokončilo se vlákno, pokračuju dál
            this.txResult.Text = vysledek.ToString();
}
  public long Calculate(long n)
        {
            long r = 0;

            for (long i = 0; i < n; i++)
                r += i;

            return r;
        }

Vše funguje jak má. Počítač počítá jak divý ale UI nezamrzlo. Nyní můj výpočet.

bmp = BitmapaNačtená ze souboru. Pixels = byte[] pixelů, size -> poměr pixel / char

manager.HTML = "Working...";
                    string result = await Task<string>.Run(() => worker.GenerateAsciiHtml(bmp, pixels, manager.Size));

                    manager.HTML = result;

Zde je metoda v jiné třídě.

public string GenerateAsciiHtml(BitmapImage bmp, byte[] pixels, int size)
        {
            StringBuilder ascii = new StringBuilder();
            ascii = new StringBuilder();
            ascii.Append(string.Format("<body style=\"font-family: 'Courier New', Courier, monospace;font-size: {0}px;\">", 5));



            for (int h = 0; h < bmp.PixelHeight - size; h += size)
            {
                for (int w = 0; w < bmp.PixelWidth - size; w += size)
                {
                    Color color = bmp.GetPixel(pixels, w, h);
                    ascii.Append(string.Format("<span style='color:rgb({0},{1},{2});'>{3}{3}</span>",
                       color.R, color.G, color.B, getAsciiChar(color.A)));

                }
                ascii.Append("<br>");
            }
            ascii.Append("</body>");


            return ascii.ToString();
        }

Skončí to chybou ihned v cyklu.

The application called an interface that was marshalled for a different thread.

Nevíte kde je problém ? Vím že asi otravuju ale já už fakt nevím :D

Nahoru Odpovědět 29.5.2013 21:06
Pokrok nezastavíš, neusni a jdi s ním vpřed
Avatar
Odpovídá na Petr Nymsa
Michal Žůrek (misaz):

Já si pořád myslím že to dělá ta knihovna třetích stran...

Nahoru Odpovědět 29.5.2013 21:11
Nesnáším {}, proto se jim vyhýbám.
Avatar
Petr Nymsa
Redaktor
Avatar
Odpovídá na Michal Žůrek (misaz)
Petr Nymsa:

Už tma žádnou nepoužívám. GetPixel je moje vlastní metoda. Všechno už je klasicky součástí WinRT

Nahoru Odpovědět 29.5.2013 21:12
Pokrok nezastavíš, neusni a jdi s ním vpřed
Avatar
Nahoru Odpovědět 29.5.2013 21:13
Nesnáším {}, proto se jim vyhýbám.
Avatar
Petr Nymsa
Redaktor
Avatar
Odpovídá na Michal Žůrek (misaz)
Petr Nymsa:

Nechci :D ... btw zjistil jsem následující. Že to volám z jiné třídy nevadí. Problém je v tom předávání Bitmapy. TU si načtu ze souboru / vyfotím kamerou a načítám / fotím ve stejné třídě jako volám tu metodu na generování. Poté to vlastně padá kvůli tomu že se snažím asi přistoupit k něčemu co je z jiného vlákna. Jak to vyřešit, nevíš ? :)

Nahoru Odpovědět 29.5.2013 21:23
Pokrok nezastavíš, neusni a jdi s ním vpřed
Avatar
Petr Nymsa
Redaktor
Avatar
Odpovídá na Michal Žůrek (misaz)
Petr Nymsa:

Tak zde radí toto http://stackoverflow.com/…ent-thread-i a zde to samý http://social.msdn.microsoft.com/…ca081921f79/ ale to zase zamrzne UI.

Už ale vím kde je problém. Ten error vyskočí vždy když se snažím editovat něco z UI Thread, teď to Image. Neívm jak udělat to aby nezmarzlo UI ale zároveň dostal tu Bitmapu ....

Nahoru Odpovědět 29.5.2013 21:35
Pokrok nezastavíš, neusni a jdi s ním vpřed
Avatar
David Dostal
Redaktor
Avatar
David Dostal:

Možná zde existuje něco jako Invoke u vláken. Ale nevím.

 
Nahoru Odpovědět 29.5.2013 23:46
Avatar
exyi
Redaktor
Avatar
Odpovídá na Petr Nymsa
exyi:

A neslo by to, kdyby se ta bitmapa nejak naklonovala? Existuje metoda MemberwiseClone(), ale nevim co presne dela :)

 
Nahoru Odpovědět 30.5.2013 7:02
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 28 zpráv z 28.