How to wait for 1 second before continuing async thread? - c#

What I'm trying to achieve here is for the thread to await at FishData fishData = await Fishing().WaitOrCancel(cancelToken); until a not null fishData is returned, else just keep on waiting and checking TryHook() again every 1 second.
I have also tried replacing the await Task.Delay(1000); with Thread.Sleep() but they just doesn't seems to work and instead kept on causing crashes. also tried InvokeRepeating and coroutines but everything got too messy and I'm not even sure I implemented them correctly.
public async Task<bool> Fish(int ID)
{
reelingSuccess = false;
FishingRodData fishingRodData = inventory.fishingRods[ID];
if (currentCell.fill == CellFill.Water)
{
CancellationToken cancelToken = new CancellationToken();
try
{
FishData fishData = await Fishing().WaitOrCancel(cancelToken);
bool reelSuccess = await ReelingFish(fishingRodData.cycleTime, fishingRodData.tolerance, fishData.cycles, fishData.hits).WaitOrCancel(cancelToken);//, fishData.speed
if (reelSuccess) print("successfully reeled in fish");
else print("failed reeling in fish! better luck next time!");
inventory.interactBtn.onClick.RemoveAllListeners();
inventory.interactBtn.onClick.AddListener(delegate { inventory.itrct(); });
}
catch (OperationCanceledException) { print("fishing has been cancelled!"); }
return true;
} return false;
}
async Task<FishData> Fishing()
{
**await Task.Delay(1000);**
print("try hook");
FishData fish = TryHook();
if (fish == null) fish = await Fishing();
return fish;
}
ps: forgive my english
EDIT: here's the TryHook Code
its basically trying to catch a fish based on probability which will mostly return a null, but once every few calls from FishData fish = TryHook(); in Fishing(), it might return a fish.
FishData TryHook()
{
FishData fish = null;
for (int i = 0; i < fishLs.Length; i++)
{
fishLs[i].minValue = inventory.fishes[i].minValue;
fishLs[i].maxValue = inventory.fishes[i].maxValue;
fishLs[i].probability = inventory.fishes[i].probability;
}
int index = GetRandomValue(fishLs);//get random index in fishLs but based on probability instead of Random.Range()
if (index != -1) { print("caught fish ind: " + index); fish = inventory.fishes[index]; }
return fish;
}
EDIT2: not sure if this is important, but this is how the Fish() is called.
void Start()
{
itrct = new interact(Interact);
fs = new Fish(activities.Fish);
rf = new removeFront(RemoveFront);
tryActivities.Add(new Func<bool>(() => activities.DoNothing()));
//tryActivities.Add(new Func<bool>(() => activities.Fish(currentSlot.basicData.typeID).Result));
tryActivities.Add(new Func<bool>(() => fs(currentSlot.basicData.typeID).Result));
tryActivities.Add(new Func<bool>(() => activities.UseStove(currentSlot.basicData.typeID)));
}
public void Interact()//id of currently hold item
{
Debug.Log("interact");
if (currentSlot != null && currentSlot.basicData.allID != -1 && TryPlaceOrUse())//check for hand activity
{
Debug.Log("successfully do in-hand activity");
}
else if (currentCell.objects.Count > 1 && currentCell.objects[^1].GetComponent<SpawnedObjectData>().allID != -1 &&
tryActivities[(int)allBasicData[currentCell.objects[^1].GetComponent<SpawnedObjectData>().allID].myActivity]())//check activity infront
{
//await tryActivities[2];
Debug.Log("successfully do in-front activity");
}
else Debug.Log("show emotes~ there's not hing to do ~~");
}

async Task<FishData> Fishing()
{
while(true)
{
await Task.Delay(1000);
print("try hook");
FishData fish = TryHook();
if (fish != null)
{
return fish;
}
}
}
and then:
var data = await Fishing();
// do what you want with your fish here

Related

What might ChannelReader<T>.ReadAny implementation look like?

BlockingCollection<T> has the handy static TakeFromAny method allowing you to consume multiple collections "I want the next item from any of these collections".
ChannelReader<T> doesn't have an equivalent so if you did want to consume multiple channels into a single stream - say to print received items to Console 1 by 1, how might this be done?
The fast path is easy, but the slow path is quite tricky. The implementation below returns a Task<ValueTuple<T, int>> that contains the value taken from one of the readers, and the zero-based index of that reader in the input array.
public static Task<(T Item, int Index)> ReadFromAnyAsync<T>(
params ChannelReader<T>[] channelReaders) =>
ReadFromAnyAsync(channelReaders, CancellationToken.None);
public static async Task<(T Item, int Index)> ReadFromAnyAsync<T>(
ChannelReader<T>[] channelReaders,
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
// Fast path
for (int i = 0; i < channelReaders.Length; i++)
{
if (channelReaders[i].TryRead(out var item)) return (item, i);
}
// Slow path
var locker = new object();
int resultIndex = -1;
T resultItem = default;
while (true)
{
using (var cts = CancellationTokenSource
.CreateLinkedTokenSource(cancellationToken, default))
{
bool availableAny = false;
Task[] tasks = channelReaders
.Select(async (reader, index) =>
{
try
{
bool available = await reader.WaitToReadAsync(cts.Token)
.ConfigureAwait(false);
if (!available) return;
}
catch // Cancellation, or channel completed with exception
{
return;
}
availableAny = true;
lock (locker) // Take from one reader only
{
if (resultIndex == -1 && reader.TryRead(out var item))
{
resultIndex = index;
resultItem = item;
cts.Cancel();
}
}
})
.ToArray();
await Task.WhenAll(tasks).ConfigureAwait(false);
if (resultIndex != -1) return (resultItem, resultIndex);
cancellationToken.ThrowIfCancellationRequested();
if (!availableAny) throw new ChannelClosedException(
"All channels are marked as completed.");
}
}
}

Task.WhenAny for non faulted tasks

The description of the Task.WhenAny method says, that it will return the first task finished, even if it's faulted. Is there a way to change this behavior, so it would return first successful task?
Something like this should do it (may need some tweaks - haven't tested):
private static async Task<Task> WaitForAnyNonFaultedTaskAsync(IEnumerable<Task> tasks)
{
IList<Task> customTasks = tasks.ToList();
Task completedTask;
do
{
completedTask = await Task.WhenAny(customTasks);
customTasks.Remove(completedTask);
} while (completedTask.IsFaulted && customTasks.Count > 0);
return completedTask.IsFaulted?null:completedTask;
}
First off, from my review there is no direct way of doing this without waiting for all the tasks to complete then find the first one that ran successfully.
To start with I am not sure of the edge cases that will cause issues that I havent tested, and given the source code around tasks and contiunuation requires more than an hour of review I would like to start to think around the follow source code. Please review my thoughts at the bottom.
public static class TaskExtensions
{
public static async Task<Task> WhenFirst(params Task[] tasks)
{
if (tasks == null)
{
throw new ArgumentNullException(nameof(tasks), "Must be supplied");
}
else if (tasks.Length == 0)
{
throw new ArgumentException("Must supply at least one task", nameof(tasks));
}
int finishedTaskIndex = -1;
for (int i = 0, j = tasks.Length; i < j; i++)
{
var task = tasks[i];
if (task == null)
throw new ArgumentException($"Task at index {i} is null.", nameof(tasks));
if (finishedTaskIndex == -1 && task.IsCompleted && task.Status == TaskStatus.RanToCompletion)
{
finishedTaskIndex = i;
}
}
if (finishedTaskIndex == -1)
{
var promise = new TaskAwaitPromise(tasks.ToList());
for (int i = 0, j = tasks.Length; i < j; i++)
{
if (finishedTaskIndex == -1)
{
var taskId = i;
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
//we dont want to await these tasks as we want to signal the first awaited task completed.
tasks[i].ContinueWith((t) =>
{
if (t.Status == TaskStatus.RanToCompletion)
{
if (finishedTaskIndex == -1)
{
finishedTaskIndex = taskId;
promise.InvokeCompleted(taskId);
}
}
else
promise.InvokeFailed();
});
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
}
return await promise.WaitCompleted();
}
return Task.FromResult(finishedTaskIndex > -1 ? tasks[finishedTaskIndex] : null);
}
class TaskAwaitPromise
{
IList<Task> _tasks;
int _taskId = -1;
int _taskCount = 0;
int _failedCount = 0;
public TaskAwaitPromise(IList<Task> tasks)
{
_tasks = tasks;
_taskCount = tasks.Count;
GC.KeepAlive(_tasks);
}
public void InvokeFailed()
{
_failedCount++;
}
public void InvokeCompleted(int taskId)
{
if (_taskId < 0)
{
_taskId = taskId;
}
}
public async Task<Task> WaitCompleted()
{
await Task.Delay(0);
while (_taskId < 0 && _taskCount != _failedCount)
{
}
return _taskId > 0 ? _tasks[_taskId] : null;
}
}
}
The code is lengthy I understand and may have lots of issues, however the concept is you need to execute all the tasks in parallel and find the first resulting task that completed successfully.
If we consider that we need to make a continuation block of all the tasks and be able to return out of the continuation block back to the original caller. My main concern (other than the fact I cant remove the continuation) is the while() loop in the code. Probably best to add some sort of CancellationToken and/or Timeout to ensure we dont deadlock while waiting for a completed task. In this case if zero tasks complete we never finish this block.
Edit
I did change the code slightly to signal the promise for a failure so we can handle a failed task. Still not happy with the code but its a start.

WaitForDrawerClose blocks printing

I'm writing an application that uses Pos for .Net, and I'm noticing that if you call WaitForDrawerClose, then you won't be able to print receipts until it has returned.
This is not desirable behavior. Is there another way to wait for the cash drawer to close without blocking the printer?
I've looked into the OnDrawerStateChanged Event, but that is a protected member of CashDrawerBase, and I'm not entirely sure how to access it.
Here is my SSCCE:
static void Main(string[] args)
{
var posExplorer = new PosExplorer();
var waitTask = WaitForCloseAsync(posExplorer);
System.Threading.Thread.Sleep(500);
PrintText(posExplorer);
waitTask.Wait();
}
public static Task WaitForCloseAsync(PosExplorer posExplorer)
{
var result = Task.Factory.StartNew(() =>
{
Console.WriteLine("waiting");
var cashDrawer = GetCashDrawer(posExplorer);
cashDrawer.Open();
cashDrawer.Claim(1000);
cashDrawer.DeviceEnabled = true;
cashDrawer.WaitForDrawerClose(10000, 4000, 500, 5000);
cashDrawer.Release();
cashDrawer.Close();
Console.WriteLine("waited");
});
return result;
}
public static void PrintText(PosExplorer posExplorer)
{
Console.WriteLine("printing");
var printer = GetPosPrinter(posExplorer);
printer.Open();
printer.Claim(1000);
printer.DeviceEnabled = true;
var text = "abc\x1B|1lF";
printer.PrintNormal(PrinterStation.Receipt, text);
printer.Release();
printer.Close();
Console.WriteLine("printed");
}
public static CashDrawer GetCashDrawer(PosExplorer posExplorer)
{
var deviceInfo = posExplorer.GetDevices(DeviceCompatibilities.Opos)
.Cast<DeviceInfo>()
.Where(d => d.Type == "CashDrawer")
.ToList();
var device = deviceInfo.FirstOrDefault(d => d.Compatibility == DeviceCompatibilities.Opos);
if (device == null)
{
return null;
}
else
return (CashDrawer)posExplorer.CreateInstance(device);
}
private static PosPrinter GetPosPrinter(PosExplorer posExplorer)
{
var deviceInfo = posExplorer.GetDevices(DeviceCompatibilities.Opos)
.Cast<DeviceInfo>()
.Where(d => d.Type == "PosPrinter")
.ToList();
var device = deviceInfo.FirstOrDefault(d => d.Compatibility == DeviceCompatibilities.Opos);
if (device == null)
{
return null;
}
else
{
return (PosPrinter)posExplorer.CreateInstance(device);
}
}
so, what I did was essentially this: Instead of using (WaitForClose), I just poll it like this:
for (var i = 0; i < 15; i++)
{
cashDrawer = GetCashDrawer(posExplorer);
cashDrawer.Open();
cashDrawer.Claim(1000);
cashDrawer.DeviceEnabled = true;
if (!cashDrawer.DrawerOpened)
{
Console.WriteLine("waited");
return;
}
cashDrawer.Release();
cashDrawer.Close();
System.Threading.Thread.Sleep(1500);
}
Console.WriteLine("timed out");
It's not ideal, but it doesn't lock the printer up either, so It'll have to to for now.

How to properly use Async, Await and ManualResetEvents to control an infinite while loop

So what i am trying to do here is:
Make the engine loop and work on an object if the queue is not empty.
If the queue is empty i call the manualresetevent to make the thread sleep.
When a item is added and the loop is not active i set the manualresetevent.
To make it faster i pick up atmost 5 items from the list and perform operation on them asynchronously and wait for all of them to finish.
Problem:
The clear methods on the two lists are called as soon as a new call to the AddToUpdateQueueMethod is called.
In my head as i am waiting for Task.WhenAll(tasks), so thread should wait for its completion before moving ahead, hence the clear on the lists should only be called on after Task.WhenAll(tasks) returns.
What am i missing here, or what will be a better way to achieve this.
public async Task ThumbnailUpdaterEngine()
{
int count;
List<Task<bool>> tasks = new List<Task<bool>>();
List<Content> candidateContents = new List<Content>();
while (true)
{
for (int i = 0; i < 5; i++)
{
Content nextContent = GetNextFromInternalQueue();
if (nextContent == null)
break;
else
candidateContents.Add(nextContent);
}
foreach (var candidateContent in candidateContents)
{
foreach (var provider in interactionProviders)
{
if (provider.IsServiceSupported(candidateContent.ServiceType))
{
Task<bool> task = provider.UpdateThumbnail(candidateContent);
tasks.Add(task);
break;
}
}
}
var results = await Task.WhenAll(tasks);
tasks.Clear();
foreach (var candidateContent in candidateContents)
{
if (candidateContent.ThumbnailLink != null && !candidateContent.ThumbnailLink.Equals(candidateContent.FileIconLink, StringComparison.CurrentCultureIgnoreCase))
{
Task<bool> task = DownloadAndUpdateThumbnailCache(candidateContent);
tasks.Add(task);
}
}
await Task.WhenAll(tasks);
//Clean up for next time the loop comes in.
tasks.Clear();
candidateContents.Clear();
lock (syncObject)
{
count = internalQueue.Count;
if (count == 0)
{
isQueueControllerRunning = false;
monitorEvent.Reset();
}
}
await Task.Run(() => monitorEvent.WaitOne());
}
}
private Content GetNextFromInternalQueue()
{
lock (syncObject)
{
Content nextContent = null;
if (internalQueue.Count > 0)
{
nextContent = internalQueue[0];
internalQueue.Remove(nextContent);
}
return nextContent;
}
}
public void AddToUpdateQueue(Content content)
{
lock (syncObject)
{
internalQueue.Add(content);
if (!isQueueControllerRunning)
{
isQueueControllerRunning = true;
monitorEvent.Set();
}
}
}
You should simply use TPL Dataflow. It's an actor framework on top of the TPL with an async support. Use an ActionBlock with an async action and MaxDegreeOfParallelism of 5:
var block = new ActionBlock<Content>(
async content =>
{
var tasks = interactionProviders.
Where(provider => provider.IsServiceSupported(content.ServiceType)).
Select(provider => provider.UpdateThumbnail(content));
await Task.WhenAll(tasks);
if (content.ThumbnailLink != null && !content.ThumbnailLink.Equals(
content.FileIconLink,
StringComparison.CurrentCultureIgnoreCase))
{
await DownloadAndUpdateThumbnailCache(content);
}
}, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 5});
foreach (var content in GetContent())
{
block.Post(content);
}
block.Complete();
await block.Completion

Cancel child tasks when one of them throws an exception

I'd like to improve the following code to add cancellation support. Basically, what I need to do is cancel all children as well as the parent task once a child throws an exception. I wrote the below code as a learning experience. I can see AggregateException only after all children finish, but I don't want that.
static int GetSum()
{
var parent = Task<int>.Factory.StartNew(() =>
{
var children = new Task<int>[100];
for (var i = 0; i < children.Length; i++)
{
var index = i;
children[index] = Task<int>.Factory.StartNew(() =>
{
var randomNumber = new Random().Next(5);
if (randomNumber == 0)
{
throw new Exception();
}
return randomNumber;
}, TaskCreationOptions.AttachedToParent);
}
Task.WaitAll();
Console.WriteLine("Children finished");
return children.Sum(t => t.Result);
});
parent.Wait();
Console.WriteLine("Parent finished");
return parent.Result;
}
I believe I need to use the following though I don't know how:
var source = new CancellationTokenSource();
var token = source.Token;
you can just use Task.WaitAny instead of WaitAll and make a cancel request to the token once an AgregateException was thrown something like this
static int GetSum()
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var parent = Task<int>.Factory.StartNew(() =>
{
var children = new Task<int>[100];
for (var i = 0; i < children.Length; i++)
{
var index = i;
children[index] = Task<int>.Factory.StartNew(() =>
{
for (int j = 0; j < 100000; j++)
{
if (!token.IsCancellationRequested)
{
var randomNumber = new Random().Next(5);
if (randomNumber == 0)
{
throw new Exception();
}
return randomNumber;
}
else
{
token.ThrowIfCancellationRequested();
}
}
return 0;
}
, token);
}
try
{
Task.WaitAny(children);
}
catch (AggregateException ae)
{
tokenSource.Cancel();
ae.Handle((task) =>
{
Console.WriteLine("Cancel all others child tasks requested ");
return true;
});
}
Console.WriteLine("Children finished");
return children.Sum(t => t.Result);
});
try
{
parent.Wait();
}
catch (AggregateException aex)
{
aex.Handle((task) =>
{
Console.WriteLine("Cancel child work done ");
return true;
});
}
Console.WriteLine("Parent finished");
return parent.Result;
}

Categories