Perl and GUI. Work with streams

    I will touch on a very sore subject, Perl + GUI + threads.
    Sore, because an attempt to get your application to work with threads may fail. The program “hangs”, “segfolit”, you will turn to the documentation, you will see that the library is not thread-safe. Was the time spent down the drain?

    Hint: create threads before calling Tkx :: MainLoop, since MainLoop () starts its event loop and blocks code execution. It would be so simple! You rewrote the code with this condition, but it still hangs ...

    What to do? There is an exit.
    You need to use the Boss / Workers model (Queue and Workers).

    Purpose: to write an application with a GUI and use multithreading.
    Let’s consider the task “on the fingers”, present everything in the form of an abstract model.

    There is a warehouse. You come to the boss (boss),
    - Hi, collect this little list for me ...
    - Okay, now I will scatter the task in parts, the workers will do everything.

    Storekeepers take tasks from the pile (and take them in the order of their receipt).

    A similar queue is implemented by a package Thread::Queue.

    We will use several methods
    - enqueue - put the task
    - dequeue, dequeue_nb - take the task

    The difference between dequeue and dequeue_nb is that the latter is non-blocking.

    In other words, when we call dequeue, we wait until the task appears, and only then we get it. And in the second case, if there is no job, then undef is returned.

    while (defined (my $ item = $ queue-> dequeue ())) {
      # perform any actions.
    }
    


    The storekeepers collected all the necessary goods, now the loader will pick it up and bring it to you.
    ...

    Now let's start implementation (simplified version).

    Task -> Tk -> Boss -> Worker -> Result

    image

    #! / usr / bin / perl
    use strict;
    use Tkx; # toolkit
    use threads; # work with threads
    use Thread :: Queue; # implements a queue
    # create queues
    my $ queue_tk = Thread :: Queue-> new (); # get tasks from Tk
    my $ queue_job = Thread :: Queue-> new (); # send to employees
    my $ queue_box = Thread :: Queue-> new (); # results
    #boss
    sub thread_boss {
        my $ self = threads-> self ();
        my $ tid = $ self-> tid ();
        while (defined (my $ item = $ queue_tk-> dequeue ())) {
            print STDERR "Boss ($ tid) has received the task from Tk: $ item \ n";
            # send the job to the employee
            $ queue_job-> enqueue ($ item);
        }
        $ queue_job-> enqueue (undef);
    }
    # employee (s)
    sub thread_worker {
        my $ self = threads-> self ();
        my $ tid = $ self-> tid ();
        while (defined (my $ job = $ queue_job-> dequeue ())) {
            print STDERR "Worker ($ tid) has received task from Boss: $ job \ n";
            # do some work ...
            print STDERR "Worker ($ tid) has finished the task \ n";
            # we drop everything in one box;)
            $ queue_box-> enqueue ("processed: $ job");
        }
        $ queue_box-> enqueue (undef);    
    }
    # create threads
    my $ boss = threads-> new (\ & thread_boss);
    my $ worker = threads-> new (\ & thread_worker);
    # Create a UI
    my $ main_window = Tkx :: widget-> new ('.');
    my $ frame = $ main_window-> new_ttk__frame (-padding => q / 10 10 10 10 /);
    $ frame-> g_grid ();
    my $ label = $ frame-> new_ttk__label (-text => 'waiting');
    $ label-> g_grid (-row => 0, -column => 0, -columnspan => 2);
    # input field
    my $ entry_data = 'enter data here';
    my $ entry = $ frame-> new_ttk__entry (-textvariable => \ $ entry_data);
    my $ button = $ frame-> new_ttk__button (
        -text => 'Send to Boss',
        -command => sub {
            $ queue_tk-> enqueue ($ entry_data);
        },
    );
    $ entry-> g_grid (-row => 1, -column => 0);
    $ button-> g_grid (-row => 1, -column => 1);
    # event handler WM_DELETE_WINDOW
    sub on_destroy {
        my $ mw = shift;
        # send undef queues, which will terminate the threads
        $ queue_tk-> enqueue (undef);
        $ queue_box-> enqueue ('finish');
        # Destroy
        # or Tkx :: destroy ('.')
        $ mw-> g_destroy ();
    }
    $ main_window-> g_wm_protocol ('WM_DELETE_WINDOW', [\ & on_destroy, $ main_window]);
    # process the result
    sub monitor {
        my $ status_lbl = shift;
        my $ result = $ queue_box-> dequeue_nb;    
        if ($ result ne 'finish') {
            if (defined $ result) {
                $ label-> configure (-text => "job completed:" .scalar (localtime));
            }
            Tkx :: after (1000, [\ & monitor, $ label]);
        }
    }
    # start monitoring
    Tkx :: after (100, [\ & monitor, $ label]);
    # unfasten threads
    # otherwise, at the end of the program, we will have warnings
    # Perl exited with active threads:
    # 2 running and unjoined
    # 0 finished and unjoined
    # 0 running and detached
    $ boss-> detach ();
    $ worker-> detach ();
    Tkx :: MainLoop ();
    


    If you plan to write a multi-threaded program for working with a network, databases, then I think that instead of standard streams, it would be much more correct to use POE (event machine, non-blocking sockets).

    While this is a draft, it will be supplemented.


    Also popular now: