【问题标题】:C#.NET using async method to prevent user call the API twice at the same timeC#.NET 使用异步方法防止用户同时调用 API 两次
【发布时间】:2021-01-12 17:25:41
【问题描述】:
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 可能会阻塞调用线程,因为它在异步调用完成之前不会返回。

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

【问题讨论】:

  • BeginEnd 类型方法是较旧的 APM 模型,它主要处理处理异步编程的较旧方法。它与线程安全、锁定或防止某人同时调用一个方法无关。您必须自己使用诸如 lock 之类的同步原语来实现它
  • @JohnWalker 你能和我们分享一下AsyncDemo 类的定义吗?您的AsyncMethodCaller 代表是如何定义的?它们的定义类似于 this 吗?
  • 这里的实际目标是什么?您是否有要保护的非线程安全 API?如果是这样,一个简单的锁或任务队列会更合适。
  • 什么浏览器??这是一个控制台应用程序。没有浏览器。没有任何阻塞,await 等待已经存在的操作完成而不阻塞调用者
  • 我怀疑您的真正问题与 BeginInvoke 或任务无关。您认为 BeginInvoke 将是解决方案,所以您问了这个问题。 实际问题是什么?

标签: c# multithreading


【解决方案1】:

如果我可以假设 AsyncDemo 是这样实现的 MSDN article:

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。
我必须定义两个辅助方法(BeginEnd)将额外的参数传递给TestMethod
End 方法返回一个元组 (string, int) 以避免 out 参数。 (1)

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
这里的workaroundTask.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.

【讨论】:

  • 或者在这两种情况下都使用Task.Run(()=>ad.TestMethod())
  • 是的,这更容易:D
  • @PeterCsala 所以这个方法只等待用户完成任务,防止用户调用API两次,我的系统可以多次登录,例如用户在Firefox中登录,在谷歌浏览器中登录,同时点击按钮请求API。这种方法能解决问题吗?
  • @JohnWalker 我提供的示例不调用任何 API。因为您的原始问题没有说明 TestMethod 做了什么,所以我无法回答您的新问题。
猜你喜欢
  • 2023-04-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-26
  • 1970-01-01
  • 2015-02-06
  • 1970-01-01
相关资源
最近更新 更多