hermes1908 has asked for the wisdom of the Perl Monks concerning the following question:

Hey guys, I am trying to write a graphical application using Tk that allows the user to download multiple files and displays progress updates for each file (i.e a simple download manager). Presently, I am trying to set up a simple progress bar that is periodically updated. I have bound the update function to a button like so

my $pbar=$mw->ProgressBar(-variable=>\$percent); $mw->Button(-text=>'Begin',-command=>[\&updateprog,\$percent])->pack; $pbar->pack;

Unfortunately this means that the main event loop waits for the update function to complete before handing control back to the user (so the button is depressed for a long period of time.). I want to make the progress update asynchronous, but don't know where to start. I suspect the answer involves threads, but I have relatively limited experience using them. On the other hand I feel that using separate threads might be overkill and that there is probably a better way to handle this (though I have yet to find it). If one of you kind souls could point me in the right direction, I would greatly appreciate it.

Thanks,

Hermes

EDIT: Jul 4, 2013

Thanks for all the help guys. There were two problems as it turns out. The first is that my Tk install was broken, I retested some code on another machine and it worked more or less as expected. The second is that I was using sleep in my tests, which as I've learned doesn't play nicely with Tk's event handling. After using repeat as suggested things seem to be working smoothly now. The only problem is that I've realized that I don't have control of the callback LWP makes, so I can't run it using Tk repeat. It looks like I will have to resort to threads.Unless anyone has a better idea.

Thanks again,
Hermes

Replies are listed 'Best First'.
Re: Perl Tk Asynchronous Progress Updates
by BrowserUk (Patriarch) on Jul 01, 2013 at 20:32 UTC
    I want to make the progress update asynchronous, but don't know where to start. I suspect the answer involves threads, but I have relatively limited experience using them. On the other hand I feel that using separate threads might be overkill and that there is probably a better way to handle this (though I have yet to find it).

    See Re: my within a loop and Tk::after


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.

      Thanks for the tip. I tried Tk::After, but that doesn't seem to work. Perhaps I misunderstood the cpan page. The modified code is below.

      my $button=$mw->Button(-text=>'Update'); $button->after(0, [\&update, \$prog ]); $button->pack; $mw->ProgressBar(-variable=>\$prog)->pack;

      For some reason the callback function is now executed immediately after the script is run (and still not asynchronously), rather than after the button event. Any help would be much appreciated.

      Thanks

        For some reason the callback function is now executed immediately... $button->after(0, [\&update, \$prog ]);

        What do you think that 0 (zero) that you have as the first argument to ->after() means?


        With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Perl Tk Asynchronous Progress Updates
by kcott (Archbishop) on Jul 02, 2013 at 04:10 UTC

    G'day hermes1908,

    Welcome to the monastery.

    I don't think your users would be too happy having to manually update the download progress by pressing a button. This is really something you'd want to display dynamically. Consider how it's done in a simple text-based scenario (e.g. wget) or in a graphical context such as your browser: you don't need to hit keys or press buttons to see what progress has been made.

    When designing a GUI, think about how it's been done previously and how your users would expect it to work. Unless you are intending to present something that is fundamentally different from the norm, stick with the status quo.

    Here's a working (albeit barebones) script that simulates 3 files being downloaded simultaneously and shows progress dynamically.

    #!/usr/bin/env perl use strict; use warnings; use Tk; use Tk::ProgressBar; my @file_data = ( { name => 'File1', size => 1000 }, { name => 'File2', size => 5000 }, { name => 'File3', size => 2500 }, ); my $mw = MainWindow->new(); $mw->geometry('400x200+50+50'); my $control_F = $mw->Frame()->pack(-side => 'bottom'); $control_F->Button(-text => 'Exit', -command => sub { exit } )->pack(-padx => 5, -pady => 5); my $progress_F = $mw->Frame( )->pack(-padx => 10, -pady => 10, -fill => 'both', -expand => 1); for my $file_datum (@file_data) { my $download_amount = 0; my $download_percent; my $download_F = $progress_F->Frame( )->pack(-padx => 10, -pady => 10, -side => 'top', -fill => 'x' +); $download_F->Label(-text => $file_datum->{name} )->pack(-padx => 5, -side => 'left'); $download_F->ProgressBar(-variable => \$download_percent )->pack(-padx => 5, -side => 'left', -fill => 'x', -expand => +1); $download_F->repeat(25 => sub { $download_amount += int rand 10; if ($download_amount > $file_datum->{size}) { $download_amount = $file_datum->{size}; } $download_percent = $download_amount / $file_datum->{size} * 1 +00; }); } MainLoop;

    -- Ken

      Thanks for the example, but the idea was that I would prompt the user for input (using an entry widget) and then initiate the download. The updates are supposed to be asynchronous so a button doesn't need to pushed to see the progress. The idea is that once a download has been initiated the update function should run in the background so as not to hang up the application (the download link is retrieved via an entry widget). Your sample code has the same issue that mine did, namely that the download routine runs before the gui is even displayed. Do you have the same issue when you run your code? Perhaps something is wrong with my version of Tk ( though I've had this problem on two different machines running different linux distributions, so that's unlikely).
        I think that you may be missing the focus of kcott's post. It is the repeat function.

        Obviously, you start the progress bar when you are ready.

        Example

        use Tk; use Tk::ProgressBar; my $mw = MainWindow->new(); my $per = 0; $mw->ProgressBar(-variable=>\$per)->pack; $mw->Button(-text=>"Go",-command=>\&GoForIt)->pack; MainLoop; ######### This is the important bit ########### sub GoForIt { $mw->repeat(25=>sub{$per+=10;return if $per>100;;$mw->update;sleep +(1)}) }

        What you're describing doesn't make any sense to me: I can't see how the (simulated) downloads in my code could have possibly run before the GUI is displayed.

        When you ran my code, did you really see all progress bars showing completion? I see them starting at the far left and moving towards the right.

        Did you change my code before running it? I can't think of any reason why the code I posted would not run, as is, under Linux; however, if you did change it, please show the actual code you are running.

        -- Ken

Re: Perl Tk Asynchronous Progress Updates
by Khen1950fx (Canon) on Jul 02, 2013 at 02:34 UTC
    Maybe this will save you time:
    #!/usr/bin/perl use strict; use warnings; use Tk; use Tk::ProgressBar; my $mw = MainWindow->new(-title => 'ProgressBar example'); my $percent_done = ''; my $progress = $mw->ProgressBar( -width => 30, -from => 0, -to => 100, -blocks => 50, -colors => [0, 'red', 50, 'white' , 80, 'blue'], -variable => \$percent_done )->pack(-fill => 'x'); $mw->Button(-text => 'Go!', -command=> sub { foreach (my $i = 0; $i < 1000; ++$i) { $percent_done = $i/10; print "$i\n"; $mw->update; } })->pack(-side => 'bottom'); MainLoop;
    Google for some more examples. Good luck.
      Thanks, but my problem is not in using the progressbar widget (I can do this successfully), but in making the callback which updates it execute asynchronously. My update function is more complicated than a simple for loop (as in your example) and takes a while to run (so I need the application to be responsive in the mean time).
        The example that I gave you can be used with threads ideally, eg., using the button in each of your threads. It shouldn't detract from your application's responsiveness. Yes, you'll need to understand threads:-).