Button with pause function

Asked

Viewed 1,209 times

3

I have a little app that inserts images into the database. There are many images and sometimes I need to stop the execution for a period and continue at another time not to disturb the client’s work.

Anyway, my application is running very well, but I would like a function that gives a pause in the for, without me losing what has already been done, and continuing where you left off. It is possible?

The code is basically this:

private void bProcessar_Click(object sender, EventArgs e)
{
    DirectoryInfo di = new DirectoryInfo(pastaOriginal);
    FileInfo[] TotalArquivos = di.GetFiles();
    int quantidade = arquivos.Length;
    for (int i = 0; i < quantidade; i++)
    {
       sqlComando = .....;

    }    
 }
  • 1

    Take a look at [tour]. You can accept an answer if it solved your problem. You can vote on every post on the site as well. Did any help you more? You need something to be improved?

4 answers

2

You can do this in several different ways! Control by database, Session or variables...

Since you don’t have the urgency to process everything immediately, I suggest you run your processing via Task with a Timer each X time, you can do as follows:

In a main method, you would create the Timer to rotate each X time, in this example, is set 1 minute:

System.Timers.Timer t = new System.Timers.Timer(TimeSpan.FromSeconds(60000).TotalSeconds);
t.AutoReset = true;
t.Elapsed += new ElapsedEventHandler(METODO_QUE_PROCESSA_ARQUIVOS);
t.Start();

You would replace the time of 60000 ms, for your desired time and would replace METODO_QUE_PROCESSA_ARQUIVOS by the name of your processing method (which you already have).

In this way, it would act as if it were an asynchronous function, but with a Timer to rotate each X time.

You can still set to process X files, each X time.

I hope it helped! Hug.

2

There is a way. In these hours people discover something called separation of concepts (in English).

Much of the code that is posted here that involves a graphical interface does processing within the interface itself. This is wrong. It may seem silly to separate but there are several reasons to do so. I will not go into detail. But you have just discovered one of the reasons why.

Just because it works doesn’t mean it’s right.

To solve this problem the processing needs to be separated from the interface. The interface should just trigger the processing, ie it should just call a method that does the processing of the files. This method must belong to the other class independent of the interface.

Well, it is possible to do a gambiarra inside this for to solve otherwise, but it’s something very bad to do and even more complicated to do right.

Normally I don’t like to just provide links but what you did is far from what needs to be done and only when you walk a little further would it help in a more specific way. Otherwise I’d have to do everything for you.

This question in the OR already gives a good indicator of how it could be done with modern techniques. The posted reply indicates an MSDN article that really shows the right way to do.

It may seem complicated, but doing it the right way is not simple.

I can think of other solutions, some without using asynchronicity, but none are simple. Processing data without blocking the UI is not something simple. It was even simplified with the use of async but pause and resume does not have a ready-made way to treat. By default there is a way to cancel the asynchronous process through a token. To consider a token to pause it is necessary to create a mechanism of its own. Fortunately someone at Microsoft thought of this.

Another example using the same technique.

I don’t like it much of that solution using another technique but is an alternative.

In that reply has an idea that does even more than the desired.

What made me think that one of the problems you might have is that it’s taking too long to process because each file is being processed individually. Parallel processing (asynchronicity already helps a lot in this) can increase processing performance at huge magnitudes. When you process files, the computer is waiting for response from the storage device and most of the time it is left doing nothing. That is, the computer has the ability to process multiple files simultaneously.

1

It is possible. Follow the example, based on this article of the MSDN.

You will need to create these classes:

public class PauseTokenSource
{
    private TaskCompletionSource<bool> m_paused;
    internal static readonly Task s_completedTask = Task.FromResult(true);

    public bool IsPaused
    {
        get { return m_paused != null; }
        set
        {
            if (value)
            {
                Interlocked.CompareExchange(
                    ref m_paused, new TaskCompletionSource<bool>(), null);
            }
            else
            {
                while (true)
                {
                    var tcs = m_paused;
                    if (tcs == null) return;
                    if (Interlocked.CompareExchange(ref m_paused, null, tcs) == tcs)
                    {
                        tcs.SetResult(true);
                        break;
                    }
                }
            }
        }
    }
    public PauseToken Token { get { return new PauseToken(this); } }

    internal Task WaitWhilePausedAsync()
    {
        var cur = m_paused;
        return cur != null ? cur.Task : s_completedTask;
    }
}

public struct PauseToken
{
    private readonly PauseTokenSource m_source;
    internal PauseToken(PauseTokenSource source) { m_source = source; }

    public bool IsPaused { get { return m_source != null && m_source.IsPaused; } }

    public Task WaitWhilePausedAsync()
    {
        return IsPaused ?
            m_source.WaitWhilePausedAsync() :
            PauseTokenSource.s_completedTask;
    }
}

So in your code:

PauseTokenSource pts = null; // esse é o token de pausa

private async void bProcessar_Click(object sender, EventArgs e)
{
    pts = new PauseTokenSource();

    var token = pts.Token;

    Task.Run(async () => await ProcessaArquivos(token));

 }

 private async Task ProcessaArquivos(PauseToken token)
 {

    DirectoryInfo di = new DirectoryInfo(pastaOriginal);
    FileInfo[] TotalArquivos = di.GetFiles();
    int quantidade = arquivos.Length;
    for (int i = 0; i < quantidade; i++)
    {
       sqlComando = .....;

       await token.WaitWhilePausedAsync(); // aguarda caso estado IsPaused 
    }    

}

Finally a method for a pause button/continue:

private void buttonPause_Click(object sender, EventArgs e)
{
    if (pts != null)
    {
        pts.IsPaused = !pts.IsPaused;
    }
}
  • Cool. I wasn’t on that pause. + 1

  • Gave error in this command: await token.Waitwhilepausedasync(); // waits for Ispaused status

  • Could you post what the mistake was? I didn’t mention it, though Task only exist from . NET version 4.0.

  • "The 'await' Operator can only be used Within an async method. Consider marking this method with the 'async' Modifier and Changing its Return type to 'Task<async>'."

  • See which method the error occurs and modify, need to add async. See the method signature: private async Task ProcessaArquivos(PauseToken token)

  • @Irenysilva The method itself that invokes another asynchronously also needs to be declared asynchronous. In the case, bProcessar_Click stays private async void bProcessar_Click. I updated the answer.

Show 1 more comment

1

Do not pause the process, but stop it completely

The ideal in a long process is to be able to pick up where you left off if you interrupt.

Otherwise, if it is unintentionally interrupted, you will either have to start and redo everything that has already been done or end up with inconsistencies like having the source data entered into the destination twice.

Imagine you pause the process and when you come back two hours later to continue the application is no longer there because the machine has been restarted.

Some ideas on how to make your process resilient to interruptions and be able to pick up where you left off:

  • Rename already processed files (for example, from img file. for processed.img file) and when it is time to start processing select only those whose name is different from that.

  • Move files already processed to another folder.

  • Create a text file where you insert the name of each already processed file. During processing then, you ignore the files that are on the list.

  • Create a table in the database for the same purpose as the text file described above. The difference is that instead of keeping in a text file the names of the files already processed, you will keep them in the database.

How to implement process interruption:

Once you have chosen the method for resuming the process, you can implement not a pause in this process but rather a complete interruption of this process. When you touch it back, it’s still where it left off. Even if your application is terminated or the machine is shut down or the database is unavailable between interruption and resumption.

    // declara um token que permite invocar o cancelamento
    CancellationTokenSource token = null;

    // declara o método do click no botão como assíncrono (async)
    private async void btnProcessar_Click(object sender, EventArgs e)
    {
        // cria uma instância do token de cancelamento
        token = new CancellationTokenSource();

        // obtém o token que será passado para o método que processa os arquivos
        CancellationToken ct = token.Token;

        // invoca o processamento dos arquivos de maneira assíncrona
        // tratando o cancelamento e os erros que possam ocorrer
        try
        {
            await Task.Run(() => ProcessaArquivos(ct));
        }
        catch (OperationCanceledException)
        {
            MessageBox.Show("Processo cancelado");
        }
        catch (Exception ex)
        {
            MessageBox.Show("O processo falhou: " + ex.ToString());
        }
    }

    private void btnCancelar_Click(object sender, EventArgs e)
    {
        // invoca o cancelamento do processamento
        if (token != null)
        {
            token.Cancel();
        }
    }

    // processamento em si
    public async Task ProcessaArquivos(CancellationToken ct)
    {
        DirectoryInfo di = new DirectoryInfo(pastaOriginal);
        FileInfo[] arquivos = di.GetFiles();
        int quantidade = arquivos.Length;
        for (int i = 0; i < quantidade; i++)
        {
            // interrompe o processo lançando exceção caso tenha sido cancelado
            ct.ThrowIfCancellationRequested();

            // sqlComando = .....;

            // aqui você registra que já foi processado segundo uma das idéias que passei
        }
    }

Actually pausing the app:

Now, if for various reasons I don’t know the pause button is more convenient, you can still use the above code with a few changes.

You should give a wider scope to control variables, as they should only be initialized the first time the process is started.

Then declare them as private in class:

private DirectoryInfo di;
private FileInfo[] arquivos;
private int quantidade;
private int quantidadeJaProcessada;

Create a method to initialize these variables only once:

private void inicializa()
{
    if (di != null) 
    {
        di = new DirectoryInfo(pastaOriginal);
        arquivos = di.GetFiles();
        quantidade = arquivos.Length;
        quantidadeJaProcessada = 0;
    }
}

On the button that triggers the processing, call the initializer before calling the process.

try
{
    inicializa();
    await Task.Run(() => ProcessaArquivos(ct));
}
// ...

And slightly change the processing loop so that it records where the process stopped and then it can continue from there:

public async Task ProcessaArquivos(CancellationToken ct)
{
    // começa do Zero ou continua de onde parou
    for (int i = quantidadeJaProcessada; i < quantidade; i++)
    {
        // verifica se foi cancelado
        if (ct.IsCancellationRequested) 
        {
            // registra quantos já foram processados, para continuar de onde parou
            quantidadeJaProcessada = i;
            ct.ThrowIfCancellationRequested();
        }

        //sqlComando = .....;
    }
}

Browser other questions tagged

You are not signed in. Login or sign up in order to post.