首页 > 解决方案 > C#.NET 使用异步方法防止用户同时调用 API 两次

问题描述

using System;
using System.Threading;

namespace Examples.AdvancedProgramming.AsynchronousOperations
{
    public class AsyncMain
    {
        public static void Main()
        {
            // The asynchronous method puts the thread id here.
            int threadId;

            // Create an instance of the test class.
            AsyncDemo ad = new AsyncDemo();

            // Create the delegate.
            AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod);

            // Initiate the asychronous call.
            IAsyncResult result = caller.BeginInvoke(3000,
                out threadId, null, null);

            Thread.Sleep(0);
            Console.WriteLine("Main thread {0} does some work.",
                Thread.CurrentThread.ManagedThreadId);

            // Call EndInvoke to wait for the asynchronous call to complete,
            // and to retrieve the results.
            string returnValue = caller.EndInvoke(out threadId, result);

            Console.WriteLine("The call executed on thread {0}, with return value \"{1}\".",
                threadId, returnValue);
        }
    }
}

EndInvoke可能会阻塞调用线程,因为它在异步调用完成之前不会返回。

我将EndInvoke在一个 API 中实现,以防止用户调用 API 两次。我是否使用正确的方法来防止同一用户同时调用 API 两次?

标签: c#multithreading

解决方案


如果我可以假设AsyncDemo在此MSDN 文章中实现了:

public class AsyncDemo
{
    // The method to be executed asynchronously.
    public string TestMethod(int callDuration, out int threadId)
    {
        Console.WriteLine("Test method begins.");
        Thread.Sleep(callDuration);
        threadId = Thread.CurrentThread.ManagedThreadId;
        return string.Format("My call time was {0}.", callDuration.ToString());
    }
}

并且相关的AsyncMethodCaller委托被定义为:

public delegate string AsyncMethodCaller(int callDuration, out int threadId);

那么您有两个选项,具体取决于 .NET 版本:

.NET 框架

这里我们可以使用Task.FromAsync( 1 ) 将 APM 模型转换为 TPL。
我必须定义两个辅助方法 ( Begin, End) 来将额外的参数传递给TestMethod.
End方法返回一个元组(string, int)以避免out参数。()

public static async Task Main()
{
    var ad = new AsyncDemo();
    AsyncMethodCaller caller = ad.TestMethod;

    IAsyncResult Begin(AsyncCallback cb, object state = null)
        => caller.BeginInvoke(3000, out _, cb, state);
    (string, int) End(IAsyncResult iar)
    {
        var result = caller.EndInvoke(out var threadId, iar);
        return (result, threadId);
    };

    var task1 = Task.Factory.FromAsync(Begin, End, null);
    var task2 = Task.Factory.FromAsync(Begin, End, null);

    await Task.WhenAll(task1, task2);

    var (result1, threadId1) = await task1;
    var (result2, threadId2) = await task1;

    Console.WriteLine($"{threadId1}: {result1}");
    Console.WriteLine($"{threadId2}: {result2}");
}

输出将是:

Test method begins.
Test method begins.
3: My call time was 3000.
3: My call time was 3000.

.NET 核心

在这里我们不能使用BeginInvoke,因为它会抛出一个PlatformNotSupportedException.
这里的解决方法是使用Task.Run.

static async Task Main(string[] args)
{
    int threadId1 = 0, threadId2 = 0;

    var ad = new AsyncDemo();
    AsyncMethodCaller caller = ad.TestMethod;

    var task1 = Task.Run(() => caller(3000, out threadId1));
    var task2 = Task.Run(() => caller(3000, out threadId2));

    await Task.WhenAll(task1, task2);

    Console.WriteLine($"{threadId1}: {await task1}");
    Console.WriteLine($"{threadId2}: {await task2}");
}

输出将是:

Test method begins.
Test method begins.
4: My call time was 3000.
5: My call time was 3000.

推荐阅读