c# - Task.Run apparently locks UI when nested too deep -


i have wpf application needs parse bunch of large xml files (around 40mb) containing products , save information products books. progress reporting, have datagrid displays filename, status ("waiting", "parsing", "completed", sort of thing), number of products found, number of parsed products, , number of books found, this:

        <datagrid grid.columnspan="2" grid.row="1" itemssource="{binding onixfiles}" autogeneratecolumns="false"                canuseraddrows="false"               canuserdeleterows="false"               canuserreordercolumns="false"               canuserresizecolumns="false"               canuserresizerows="false"               canusersortcolumns="false">         <datagrid.columns>             <datagridtextcolumn header="bestand" isreadonly="true" binding="{binding filename}" sortmemberpath="filename" />             <datagridtextcolumn header="status" isreadonly="true" binding="{binding status}" />             <datagridtextcolumn header="aantal producten" isreadonly="true" binding="{binding numtotalproducts}" />             <datagridtextcolumn header="verwerkte producten" isreadonly="true" binding="{binding numparsedproducts}" />             <datagridtextcolumn header="aantal geschikte boeken" isreadonly="true" binding="{binding numsuitablebooks}" />                         </datagrid.columns>     </datagrid> 

when hit "parse" button, want iterate through list of filenames , parse each file, reporting amount of products, parsed products , found books along way. want ui remain responsive want parsing on different thread using task.run().

when user hits button labeled "parse", application needs start parsing files. if call taskrun right in button command's command_executed method works fine:

    private async void parsefilescommand_executed(object sender, executedroutedeventargs e)     {         foreach (var f in onixfiles)         {             await task.run(() => f.parse());         }     }      // in onixfileviewmodel     public void parse()     {         var progressindicator = new progress<parsingprogress>(reportprogress);         var books = parser.parsefile(this.filename, progressindicator);     }      private void reportprogress(parsingprogress progress)     {         // these properties notify ui of changes         numtotalproducts = progress.numtotalproducs;         numparsedproducts = progress.numparsedproducts;         numsuitablebooks = progress.numsuitablebooks;     }      // in class parser public static ienumerable<book> parsefile(string filepath, iprogress<parsingprogress> progress)     {         list<book> books = new list<book>();          var root = xelement.load(filepath);         var fileinfo = new fileinfo(filepath);         xnamespace defaultnamespace = "http://www.editeur.org/onix/3.0/reference";          var products = (from p in xelement.load(filepath).elements(defaultnamespace + "product")                         select p).tolist();          var parsingprogress = new parsingprogress()         {             numparsedproducts = 0,             numsuitablebooks = 0,             numtotalproducs = products.count         };          progress.report(parsingprogress);          foreach (var product in products)         {             // complex xml parsing goes here             parsingprogress.numparsedproducts++;              if (...) // if parsed product actual book             {                   parsingprogress.numsuitablebooks++;                              }              progress.report(parsingprogress);         }          return books;     } 

it executes super-fast, ui gets updated , remains responsive. however, if move call task.run() parsefile method, so:

    private async void parsefilescommand_executed(object sender, executedroutedeventargs e)     {         foreach (var f in onixfiles)         {             await f.parseasync();         }     }      // in onixfileviewmodel     public async task parseasync()     {         var progressindicator = new progress<parsingprogress>(reportprogress);         var books = await parser.parsefileasync(this.filename, progressindicator);     }      private void reportprogress(parsingprogress progress)     {         // these properties notify ui of changes         numtotalproducts = progress.numtotalproducs;         numparsedproducts = progress.numparsedproducts;         numsuitablebooks = progress.numsuitablebooks;     }      // in class parser public static async task<ienumerable<book>> parsefileasync(string filepath, iprogress<parsingprogress> progress)     {         list<book> books = new list<book>();          await task.run(() =>         {          var root = xelement.load(filepath);         var fileinfo = new fileinfo(filepath);         xnamespace defaultnamespace = "http://www.editeur.org/onix/3.0/reference";          var products = (from p in xelement.load(filepath).elements(defaultnamespace + "product")                         select p).tolist();          var parsingprogress = new parsingprogress()         {             numparsedproducts = 0,             numsuitablebooks = 0,             numtotalproducs = products.count         };          progress.report(parsingprogress);          foreach (var product in products)         {             // complex xml parsing goes here             parsingprogress.numparsedproducts++;              if (...) // if parsed product actual book             {                   parsingprogress.numsuitablebooks++;                              }              progress.report(parsingprogress);         }         });          return books;     } 

the ui locks up, doesn't update until after file has finished parsing, , appears slower.

what missing? why work expected if call task.run() in command_executed handler, not if call in async method called method?

edit: requested shaamaan, here's simpler sample of i'm doing (using thread.sleep simulate workload) frustratingly, sample works had expected to, failing highlight problem i'm having. still, adding completeness:

mainwindow.xaml:

<window x:class="threadingsample.mainwindow"         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"         title="mainwindow" height="350" width="525">     <stackpanel>          <datagrid grid.columnspan="2" grid.row="1" itemssource="{binding things}" autogeneratecolumns="false"                    height="250"                   canuseraddrows="false"                   canuserdeleterows="false"                   canuserreordercolumns="false"                   canuserresizecolumns="false"                   canuserresizerows="false"                   canusersortcolumns="false">             <datagrid.columns>                 <datagridtextcolumn header="name" isreadonly="true" binding="{binding name}" />                 <datagridtextcolumn header="value" isreadonly="true" binding="{binding value}" />                             </datagrid.columns>         </datagrid>          <button click="rightbutton_click">right</button>         <button click="wrongbutton_click">wrong</button>     </stackpanel> </window> 

mainwindow.xaml.cs:

using system; using system.collections.generic; using system.collections.objectmodel; using system.linq; using system.text; using system.threading; using system.threading.tasks; using system.windows; using system.windows.controls; using system.windows.data; using system.windows.documents; using system.windows.input; using system.windows.media; using system.windows.media.imaging; using system.windows.navigation; using system.windows.shapes;  namespace threadingsample {     /// <summary>     /// interaction logic mainwindow.xaml     /// </summary>     public partial class mainwindow : window     {         public observablecollection<thing> things { get; private set; }          public mainwindow()         {             initializecomponent();              this.datacontext = this;              things = new observablecollection<thing>();              (int = 0; < 200; i++)             {                 things.add(new thing(i));             }         }          private async void rightbutton_click(object sender, routedeventargs e)         {             foreach (var t in things)             {                 await task.run(() => t.parse());             }         }          private async void wrongbutton_click(object sender, routedeventargs e)         {             foreach (var t in things)             {                 await t.parseasync();             }                     }     } } 

thing.cs:

using system; using system.collections.generic; using system.componentmodel; using system.linq; using system.text; using system.threading; using system.threading.tasks;  namespace threadingsample {     public class thing : inotifypropertychanged     {         private string _name;          public string name         {             { return _name; }             set             {                 _name = value;                 raisepropertychanged("name");             }         }          private int _value;          public int value         {             { return _value; }             set             {                 _value = value;                 raisepropertychanged("value");             }         }          public thing(int number)         {             name = "thing nr. " + number;             value = 0;         }          public void parse()         {             var progressreporter = new progress<int>(reportprogress);             heavyparsemethod(progressreporter);         }          public async task parseasync()         {             var progressreporter = new progress<int>(reportprogress);             await heavyparsemethodasync(progressreporter);         }          private void heavyparsemethod(iprogress<int> progressreporter)         {             (int = 0; < 1000; i++)             {                 thread.sleep(10);                 progressreporter.report(i);             }         }          private async task heavyparsemethodasync(iprogress<int> progressreporter)         {             await task.run(() =>                 {                     (int = 0; < 1000; i++)                     {                         thread.sleep(100);                         progressreporter.report(i);                     }                 });         }          private void reportprogress(int progressvalue)         {             this.value = progressvalue;         }          private void raisepropertychanged(string propertyname)         {             if (propertychanged != null)             {                 propertychanged(this, new propertychangedeventargs(propertyname));             }         }          public event propertychangedeventhandler propertychanged;     } } 

the difference between sample , real-life code, can tell, real-life code parses bunch of 40mb xml files using linq xml whereas sample calls thread.sleep().

edit 2: i've found horrifying workaround. if use second method , call thread.sleep(1) after each product parsed , before calling iprogress.report(), works fine. can see "numparsedproducts" counter increase , everything. terrible hack though. mean?

every time call progress.report(...) posting message ui thread update ui , because calling in tight loop flooding ui thread report messages needs process , not time else (and locking up). why thread.sleep(1) 'hack' working, because giving ui thread time catch up.

you need rethink way report or @ least how post back. use many techniques of buffering post backs. have used solution reactive extensions


Comments

Popular posts from this blog

blackberry 10 - how to add multiple markers on the google map just by url? -

php - guestbook returning database data to flash -

delphi - Dynamic file type icon -