一个不能被同时调用的Task,怎样将需要完成的工作排队给它执行?

中文命名法 2021-05-04 08:49:36
class 有个问题
{
Task 任务发送; // 同一时间只能被调用一次
public async void 命令发送(string 命令)
{
任务发送 = ClientWebSocket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(命令)), WebSocketMessageType.Text, true, new CancellationToken());
await 任务发送;
}
}
因为Task 任务发送不可以被多个线程同时调用,所以,我要写一个可以被多线程同时调用的方法,去应对并发。请问这要怎么处理,将并发改为一个一个处理的任务?
...全文
336 点赞 收藏 11
写回复
11 条回复
xiaoxiangqing 05月10日
有先后顺序的比较麻烦
回复 点赞
xuzuning 05月10日
排队 无论你采用何种算法,为了防止公共资源对的竞争,只能是排队
回复 点赞
这是一个很重要、很基本的知识。如果你使用 Task 搞稍微大一点的系统设计开发,不可不知道。
回复 点赞
这是一个极其重要的知识! 对于普通的代码,其实我们可以使用 lock(){..} 之类的结构来方便地写线程同步代码。而对于异步代码,则要使用 SemaphoreSlim,它能指定最多只允许几个 Task 进入管理区。
回复 点赞
bloodish 05月07日
把需要发送的命令放入BlockingCollection并由一个发送Task进行消费(发送命令).

         static void Main(string[] args)
        {
            //add task item
            var taskItems = new BlockingCollection<string>();

            Task.Run(async () =>
            {
                var guid = Guid.NewGuid().ToString();
                taskItems.TryAdd(guid);
                //add some delay time
                await Task.Delay(Math.Abs(guid.GetHashCode())%5000);
            });

            //consume item
            Task.Run(async () =>
            {
                foreach(var item in taskItems.GetConsumingEnumerable())
                {
                    await Task.Run(() =>
                    {
                        //ClientWebSocket.SendAsync
                    });
                }
            });

            Console.Read();
        }
回复 点赞
中文命名法 05月06日
@wanghui0380 谢谢,知识量很大,容我消化一下。
回复 点赞
wanghui0380 05月05日
在来看微软的源代码
private async Task SendAsyncCore(ArraySegment<byte> buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken)
	{
		string inputParameter = string.Empty;
		if (s_LoggingEnabled)
		{
			inputParameter = string.Format(CultureInfo.InvariantCulture, "messageType: {0}, endOfMessage: {1}", new object[2] { messageType, endOfMessage });
			Logging.Enter(Logging.WebSockets, this, "SendAsync", inputParameter);
		}
		try
		{
			ThrowIfPendingException();
			ThrowIfDisposed();
			WebSocket.ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseReceived);
			bool ownsCancellationTokenSource = false;
			CancellationToken linkedCancellationToken = CancellationToken.None;
			try
			{
				while (true)
				{
					bool flag;
					ownsCancellationTokenSource = (flag = m_SendOutstandingOperationHelper.TryStartOperation(cancellationToken, out linkedCancellationToken));
					if (flag)
					{
						break;
					}
					Task keepAliveTask;
					lock (SessionHandle)
					{
						keepAliveTask = m_KeepAliveTask;
						if (keepAliveTask == null)
						{
							m_SendOutstandingOperationHelper.CompleteOperation(ownsCancellationTokenSource);
							ownsCancellationTokenSource = (flag = m_SendOutstandingOperationHelper.TryStartOperation(cancellationToken, out linkedCancellationToken));
							if (flag)
							{
								break;
							}
							throw new InvalidOperationException(SR.GetString("net_Websockets_AlreadyOneOutstandingOperation", "SendAsync"));
						}
					}
					await keepAliveTask.SuppressContextFlow();
					ThrowIfPendingException();
					m_SendOutstandingOperationHelper.CompleteOperation(ownsCancellationTokenSource);
				}
				if (s_LoggingEnabled && buffer.Count > 0)
				{
					Logging.Dump(Logging.WebSockets, this, "SendAsync", buffer.Array, buffer.Offset, buffer.Count);
				}
				_ = buffer.Offset;
				EnsureSendOperation();
				m_SendOperation.BufferType = GetBufferType(messageType, endOfMessage);
				await m_SendOperation.Process(buffer, linkedCancellationToken).SuppressContextFlow();
			}
			catch (Exception exception)
			{
				bool isCancellationRequested = linkedCancellationToken.IsCancellationRequested;
				Abort();
				ThrowIfConvertibleException("SendAsync", exception, cancellationToken, isCancellationRequested);
				throw;
			}
			finally
			{
				m_SendOutstandingOperationHelper.CompleteOperation(ownsCancellationTokenSource);
			}
		}
		finally
		{
			if (s_LoggingEnabled)
			{
				Logging.Exit(Logging.WebSockets, this, "SendAsync", inputParameter);
			}
		}
	}
你可以看到他内部是加锁的,同时一个关键核心
public bool TryStartOperation(CancellationToken userCancellationToken, out CancellationToken linkedCancellationToken)
		{
			linkedCancellationToken = CancellationToken.None;
			ThrowIfDisposed();
			lock (m_ThisLock)
			{
				if (++m_OperationsOutstanding == 1)
				{
					linkedCancellationToken = CreateLinkedCancellationToken(userCancellationToken);
					return true;
				}
				return false;
			}
		}

		public void CompleteOperation(bool ownsCancellationTokenSource)
		{
			if (m_IsDisposed)
			{
				return;
			}
			CancellationTokenSource cancellationTokenSource = null;
			lock (m_ThisLock)
			{
				m_OperationsOutstanding--;
				if (ownsCancellationTokenSource)
				{
					cancellationTokenSource = m_CancellationTokenSource;
					m_CancellationTokenSource = null;
				}
			}
			cancellationTokenSource?.Dispose();
		}
依旧还是加锁的,并且m_OperationsOutstanding这玩意其实就是一个信号量控制
回复 点赞
wanghui0380 05月05日
1.从来就没听说过ClientWebSocket.SendAsync 不能同时调用的说法。这是一个IO异步。实际调用网卡驱动完成,而网卡驱动通过dma直接进行内存访问。你写入的send只是写着发送缓冲区,而发送缓冲区通过缓冲区申请可用缓冲地址标记。所以核心上并不存在不能同时调用的说法 2.如果业务上必须是一问一答式的“同步”完成,你可用异步信号量直接“加锁”。为了避免你搜到博客园一堆不靠谱的文章,我们直接给关键词你在去搜“SemaphoreSlim”
回复 点赞
中文命名法 05月05日
引用 1 楼 Eason0807 的回复:
没理解你要干什么
能理解了吗,最终要调用ClientWebSocket.SendAsync,它不能同一时间上被调用2次以上,所以要怎么改写async void 命令发送(string 命令),让来自于不同线程上的并发,转换成排队调用ClientWebSocket.SendAsync的方式执行。
回复 点赞
中文命名法 05月05日
引用 1 楼 Eason0807 的回复:
Parallel.Invoke(() => Task1(), () => Task2(), () => Task3());
因为这个Task很特殊,它会向服务端发送数据,不能同时运行,Parallel.Invoke将它同时运行,这个Task会报错。但是业务中存在多线程调用这个Task的需求,所以,问题就是怎样将对于这个Task多线程的调用转换为单线程排队的调用。
回复 点赞
Eason0807 05月04日
没理解你要干什么 Parallel.Invoke(() => Task1(), () => Task2(), () => Task3());
回复 点赞
发动态
发帖子
C#
创建于2007-09-28

8.5w+

社区成员

64.0w+

社区内容

.NET技术 C#
社区公告
暂无公告