我有一个充当网站控制器的类。这些交易所接受订单(想想购物车)。有些站点只允许您一次拥有一个活动的订单/购物车,而其他站点则没有限制。
我的程序将有需要订购/添加到购物车的恒定物品流。对于可以有无限数量的并发订单的网站来说,这不是问题,因为我可以在收到商品请求时启动一个新订单,但是对于数量有限的网站,商品将在队列中累积,以等待当前订单完成后进行处理。以下是我到目前为止的代码,尽管可以承认虽然可能存在一些我不知道的漏洞或陷阱(如果是这种情况,请指出),但我认为它可以正常工作,并且可以完成工作。
但是,由于一个项目或排队的项目的处理只能通过添加一个项目来触发,而while(Enabled)似乎无效并且对我来说是错误的。我想重组必须包含此逻辑的内容,但是我不确定如何以线程安全的方式处理并发限制。
class OrderItemTask
{
public OrderStatus Status { get; private set; }
public OrderItemParams Params { get; }
}
class SiteController
{
public bool Enabled { get; private set; }
//set in constructor, or options somewhere
SemaphoreSlim maxConcurrency = new SemaphoreSlim( 1, 1 );
Queue<OrderItemTask> itemQueue = new Queue<OrderItemTask>();
public void Start()
{
lock (this)
{
Enabled = true;
Task.Run( () => ProcessOrderWorker() );
}
}
public void Stop()
{
lock (this)
{
Enabled = false;
}
}
public OrderItemTask OrderItem(OrderItemParams @params)
{
var task = new OrderItemTask() { Params = @params};
QueueItem( task );
return task;
}
void QueueItem(OrderItemTask task)
{
lock (itemQueue)
{
itemQueue.Enqueue( task );
}
}
async Task ProcessOrderWorker()
{
//while true or while SiteController is enabled
while (Enabled)
{
try
{
await maxConcurrency.WaitAsync();
lock (itemQueue)
{
if (itemQueue.Count > 0)
{
var itemBatch = new List<OrderItemTask>();
while (itemQueue.Count > 0)
itemBatch.Add( itemQueue.Dequeue() );
placeOrderStrategy.PlaceOrder( itemBatch );
}
}
}
catch(Exception e)
{ }
finally
{
maxConcurrency.Release();
}
}
}
答案 0 :(得分:0)
请查看评论。我已进行更改,因此可以解决您的问题
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Trader.Classes
{
public class OrderItemParams
{
}
public class OrderStatus
{
}
public class SemaphoreSlim
{
public SemaphoreSlim(int a, int b)
{
}
internal Task WaitAsync()
{
//do stuff, not sure what?
throw new NotImplementedException();
}
internal void Release()
{
throw new NotImplementedException();
}
}
class OrderItemTask
{
public OrderStatus Status
{
get;
private set;
}
public OrderItemParams Params
{
get;
/*Sa6a: Added the set, as you were trying to assign it in public "OrderItemTask OrderItem(OrderItemParams @params)"
and it was telling you it is readonly */
set;
}
}
class SiteController
{
public bool Enabled { get; private set; }
//set in constructor, or options somewhere
SemaphoreSlim maxConcurrency = new SemaphoreSlim(1, 1);
//Queue<OrderItemTask> orderQueue = new Queue<OrderItemTask>();
ConcurrentQueue<OrderItemTask> orderQueue = new ConcurrentQueue<OrderItemTask>(); //Sa6a: NET 4.0 and above
List<Task> tList = new List<Task>(); //Sa6a: It is good to have reference to the tasks you are starting, so you can manage them better
/*Sa6a: I think you do not need that look at the QueueOrder method, I am leaving only start to true for now,
so we know that the whole thing is started and we can have a way to stop it if we want */
public void Start()
{
Enabled = true;
//lock (this) //Sa6a: you do not need this lock, as this is not a method that can be accessed by more than one thread at a time
//{
//if (Enabled == false) //Sa6a: this might already be true, and if this is the case, you will end up starting another process
//{
// Enabled = true;
// Task t = Task.Run(() => ProcessOrderWorker());
// tList.Add(t);
//}
//}
}
public void Stop()
{
//lock (this) //Sa6a: you do not need this lock, as this is not a method that can be accessed by more than one thread at a time
//{
Enabled = false;
tList.Clear();
//}
}
public OrderItemTask OrderItem(OrderItemParams @params)
{
var task = new OrderItemTask() { Params = @params };
QueueOrder(task);
return task;
}
public void QueueOrder(OrderItemTask task)
{
/* Sa6a: MSN documentation does not say that Eqnqueue can throw an error, but call it a 6th sense, just have the catch
* https://docs.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentqueue-1?redirectedfrom=MSDN&view=netframework-4.8
*/
try
{
if (Enabled)
{
//Remove the completed tasks
foreach(Task t in tList)
{
if (t.IsCompleted)
{
tList.Remove(t);
}
}
orderQueue.Enqueue(task);
//Sa6a: Start the task here if it is not runningg anymore.
if (tList.Count == 0)
{
Task t = Task.Run(() => ProcessOrderWorker());
tList.Add(t);
} }
else
{
Console.WriteLine("I do not know if we need this, but the idea is if this is stopped, " +
"you should stop enqueuing items, and you can do this onkly if the start method is called. Another way would be to set Enabled to true automatically here?");
}
}
catch (Exception ex)
{
Console.WriteLine("This should never happen in theoery - " + ex);
}
}
/* Sa6a: So the way I have it below is that once the items are dequeued, it should exit the task,
* however I am not sure what these maxConcurrency objects do, so if they make it run again, you should remove them
*/
async Task ProcessOrderWorker()
{
/* Sa6a: You can put this somewhere outside or anywhere else,
* The idea is that while, items are being added to the queue, they are being dequeued,
* so without this you might end up in an infinite loop, if items are being added constantly and fater than they
* are being dequeued(do not argue how fast the dequeue goes, does not matter)
*/
int num_tasks_in_a_batch = 100;
int counter = 0;
//while true or while SiteController is enabled
//while (Enabled) --> Sa6a: you should not need this. If an item is added to the queue and the queue has item, the method will be called again
//{
try
{
await maxConcurrency.WaitAsync(); //Sa6a: What is this for?
//lock (orderQueue) //Sa6a: I do not think you need a lock on the queue if it is concurrent
//{
if (orderQueue.Count > 0)
{
var batch = new List<OrderItemTask>();
while (orderQueue.Count > 0)
{
OrderItemTask my_task = null;
if (orderQueue.TryDequeue(out my_task))
{
batch.Add(my_task);
}
/* Sa6a: Explanation of this above, where the variables got declared
*/
if (counter == num_tasks_in_a_batch)
{
placeOrderStrategy.PlaceOrder(batch); //Sa6a: I guess this is what you might believe would take all the time, and while this is happening items can be added to the queue
batch = new List<OrderItemTask>();
}
/*Sa6a: As the queue is concurrent and you will end up adding items, while they are being add here,
* the user might try to stop the application and you might want to let them
*/
if (Enabled == false)
{
break; //it should exit out of here
}
}
//Sa6a: You might have had 120 items in the queue. The first 100 got placed, now this will place the remainder 20
if (batch.Count > 0)
{
placeOrderStrategy.PlaceOrder(batch); //Sa6a: I guess this is what you might believe would take all the time, and while this is happening items can be added to the queue
}
}
//}
}
catch (Exception e)
{
Console.WriteLine("Let's printed it, so we know it has happened, if it ever does - " + ex);
}
finally
{
maxConcurrency.Release(); //Sa6a: I do not know what this is for either
}
//}
}
答案 1 :(得分:0)
如果您不介意同步阻止,则可以使用Monitor Wait/Pulse的强大功能(尽管级别很低)。您将在方法ProcessOrderWorker
中等待,并且每次更改允许处理更多项目的条件时,都会产生脉冲。这是等待的部分:
void ProcessOrderWorker()
{
while (true)
{
lock (orderQueue) // acquire the lock
{
while (!Enabled || orderQueue.Count == 0) // waiting condition
{
Monitor.Wait(orderQueue); // here the lock is released
// while waiting for a pulse
}
}
maxConcurrency.Wait();
lock (orderQueue)
{
if (orderQueue.Count > 0)
{
var batch = new List<OrderItemTask>();
while (orderQueue.Count > 0)
batch.Add(orderQueue.Dequeue());
placeOrderStrategy.PlaceOrder(batch);
}
}
maxConcurrency.Release();
}
}
这是您应该脉动的位置:
public void Start()
{
lock (orderQueue)
{
Enabled = true;
Monitor.Pulse(orderQueue);
}
}
public void Stop()
{
lock (orderQueue)
{
Enabled = false;
Monitor.Pulse(orderQueue);
}
}
void QueueOrder(OrderItemTask task)
{
lock (orderQueue)
{
orderQueue.Enqueue(task);
Monitor.Pulse(orderQueue);
}
}