简体   繁体   中英

How to best prevent running async method again before it completes?

I've got this pattern for preventing calling into an async method before it has had a chance to complete previously.

My solution involving needing a flag, and then needing to lock around the flag, feels pretty verbose. Is there a more natural way of achieving this?

public class MyClass
{
    private object SyncIsFooRunning = new object();
    private bool IsFooRunning { get; set;}

    public async Task FooAsync()
    {
        try
        {
            lock(SyncIsFooRunning)
            {
                if(IsFooRunning)
                    return;

                IsFooRunning = true;
            }

            // Use a semaphore to enforce maximum number of Tasks which are able to run concurrently.
            var semaphoreSlim = new SemaphoreSlim(5);
            var trackedTasks = new List<Task>();

            for(int i = 0; i < 100; i++)
            {
                await semaphoreSlim.WaitAsync();

                trackedTasks.Add(Task.Run(() =>
                {
                    // DoTask();
                    semaphoreSlim.Release();
                }));
            }

            // Using await makes try/catch/finally possible.
            await Task.WhenAll(trackedTasks);
        }
        finally
        {
            lock(SyncIsFooRunning)
            {
                IsFooRunning = false;
            }
        }
    }
}

As noted in the comments, you can use Interlocked.CompareExchange() if you prefer:

public class MyClass
{
    private int _flag;

    public async Task FooAsync()
    {
        try
        {
            if (Interlocked.CompareExchange(ref _flag, 1, 0) == 1)
            {
                return;
            }

            // do stuff
        }
        finally
        {
            Interlocked.Exchange(ref _flag, 0);
        }
    }
}

That said, I think it's overkill. Nothing wrong with using lock in this type of scenario, especially if you don't expect a lot of contention on the method. What I do think would be better is to wrap the method so that the caller can always await on the result, whether a new asynchronous operation was started or not:

public class MyClass
{
    private readonly object _lock = new object();
    private Task _task;

    public Task FooAsync()
    {
            lock (_lock)
            {
                return _task != null ? _task : (_task = FooAsyncImpl());
            }
    }

    public async Task FooAsyncImpl()
    {
        try
        {
            // do async stuff
        }
        finally
        {
            lock (_lock) _task = null;
        }
    }
}

Finally, in the comments, you say this:

Seems a bit odd that all the return types are still valid for Task?

Not clear to me what you mean by that. In your method, the only valid return types would be void and Task . If your return statement(s) returned an actual value, you'd have to use Task<T> where T is the type returned by the return statement(s).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM