So, i was making a discord bot that would be able to change a role's colour every after a certain amount of time. it ran properly, however, the bot is unable to register other commands or run any other work while this task is being run. (it wont stop when the stop command is run as it couldn't get in there in the first place).
i was hoping someone here could help fix this problem.
so here's part of the code:
[Group("rainbow")]
public class Rainbow : ModuleBase<SocketCommandContext>
{
public Boolean enabled;
[Command, RequireUserPermission(GuildPermission.ManageRoles)]
public async Task rainbowAsync(SocketRole role, int milliseconds)
{
if (milliseconds >= 500) {
enabled = true;
while (enabled)
{
Random rnd = new Random();
double id = rnd.NextDouble() * 7;
if (id < 1)
{
await role.ModifyAsync(a => a.Color = Discord.Color.Red);
}
else if (id < 2)
{
await role.ModifyAsync(a => a.Color = Discord.Color.Orange);
}
else if (id < 3)
{
await role.ModifyAsync(a => a.Color = Discord.Color.LightOrange);
}
else if (id < 4)
{
await role.ModifyAsync(a => a.Color = Discord.Color.Green);
}
else if (id < 5)
{
await role.ModifyAsync(a => a.Color = Discord.Color.Blue);
}
else if (id < 6)
{
await role.ModifyAsync(a => a.Color = Discord.Color.DarkBlue);
}
else if (id < 7)
{
await role.ModifyAsync(a => a.Color = Discord.Color.Purple);
}
await Task.Delay(milliseconds);
}
}
else { await ReplyAsync("Time value must be higher than 500ms"); }
}
[Command("stop"), RequireUserPermission(GuildPermission.ManageRoles)]
public async Task StopAsync()
{
enabled = false;
await ReplyAsync("Rainbow effect stopped");
}
}
As Alex mentioned in his comment,
Your command goes into an infinite loop.
if (milliseconds >= 500) {
enabled = true;
while (enabled)
{
//Code
await Task.Delay(milliseconds);
}
}
This puts your command into an infinite loop which prevents other commands from firing. The reason this prevents other commands from firing in an Async environment is because Async is still just a single thread and the default way Discord.net calls your command is await Command();
When an async call is awaited it blocks the call until the Task completes. Your task will never complete because of the infinite loop.
There are a couple different ways to handle this, but the way I would suggest to handle it is to schedule the recoloring. Look into ways to schedule tasks to happen every x time-frame.
As an example
public class TimerTask
{
private readonly Timer _taskTimer;
public TimerTask(Action action, int interval = 10000)
{
_taskTimer = new Timer { AutoReset = true, Interval = interval };
_taskTimer.Elapsed += (_, __) => action();
}
public void Start() { _taskTimer.Start(); }
public void Stop() { _taskTimer.Stop(); }
}
Related
Ok. I know that I should not fire an Event inside a Getter, but I'm drafting code in LinqPad and Dotnetfiddle, so I want to be concise, and not create another method.
I have a loop routine that:
await Task.Run(()
=> RepeatAction.Every(() =>
{
if (!this.AllCyclesEnded || NumberOfSteps == 1)
{
if (NumberOfSteps > 1) ++Counter;
var tidalVolume = 500; var plateauPressure = SimulatePlateauAdjustment(12f, this.CurrentPEEP);
//var plateauPressure = 29;
var thisCycle = MeasurementCycle.Creator(this.CurrentPEEP, tidalVolume, plateauPressure);
if (!IsIdealPEEP)
CycleIterator(thisCycle);
}
},
TimeSpan.FromSeconds(StepDuration), cancellation.Token).Wait()
, abortedCyclesBecauseIdealPEEPFound.Token);
And depending of some things that CycleIterator(thisCycle); does, it fires some Events:
public bool AllCyclesEnded
{
get
{
if (HaveAllCyclesEnded && !CyclesEndedEventFired)
{
CyclesEndedEventFired = true;
OnCycleEnded(new CycleEndedEventArgs(Cycle.Count > 0 ? Cycle.LastOrDefault() : default
, this.Counter, this.CurrentPEEP, this.EndPEEP
, this.AllCyclesEnded, this.CyclePhase));
Console.WriteLine($"\t\tEnded # {this.Counter} loops with currentPEEP of {this.CurrentPEEP} in {this.CyclePhase} phase.");
//Without above, neither of these events fire...
OnCyclesEnded(new CyclesEndedEventArgs(Cycle.Count > 0 ? Cycle.LastOrDefault() : default
, this.Counter, this.CurrentPEEP, this.EndPEEP, this.CyclePhase));
}
return HaveAllCyclesEnded;
}
}
This piece of code above only fires IF I left the Console.WriteLine there; if not, Getter is called (AllCyclesEnded == true), but Events are not Fired...
Any hints?
EDIT2: Console.WriteLine solve the problem in a non deterministically way; about 60% of runs...
EDIT1 after Charlieface comments; it is a Task that returns an Action:
public static class RepeatAction
{
public static async Task Every(Action action,
TimeSpan interval, CancellationToken cancellationToken)
{
while (true)
{
action();
Task task = Task.Delay(interval, cancellationToken);
try
{
await task;
}
catch (TaskCanceledException)
{
return;
}
}
}
}
I have a problem that is when I call an async method, it enters and comes up against a condition that I have set, where it sends me an exception that says:
"The subprocess making the call can not access this object because the
owner is another thread.",
I just want the condition process to run in the background with the async method
private Task ObtenerDatosd()
{
return Task.Run(() =>
{
for (int i = draw2.Count - 1; i >= 0; i--)
{
if (draw2[i].ToString().ToLower().Contains(SearcInterno.Text.ToLower()))
{
//action
System.Windows.MessageBox.Show("Code action");
}
}
});
}
You cannot access WPF objects from another thread without a dispatcher. But you don't need it in this case, if you just use:
private Task ObtenerDatosd()
{
string text = SearcInterno.Text.ToLower();
return Task.Run(() =>
{
for (int i = draw2.Count - 1; i >= 0; i--)
{
if(draw2[i].ToString().ToLower().Contains(text))
{
//action
System.Windows.MessageBox.Show("Code action");
}
}
});
}
public IDisposable subscription;
public ProcessData(DeviceModel model, string name = "") : base(model, name)
{
BaseAddress = _model.Process.oxyVal.regAddr;
CurrentSampleRate=1000;
subscription = Observable.Interval(TimeSpan.FromMilliseconds(CurrentSampleRate)).Subscribe(async t => await Refresh());
}
public async Task Refresh()
{
Status = "Fetching from device...";
if ((_model == null) || !_model.IsSerialPortOpen || busy)
{
StatusTrans = "Device not connected.";
return;
}
busy = true;
try
{
await ReadFromDevice().ConfigureAwait(false);
StatusTrans = "Completed.";
}
catch (Exception ex)
{
StatusTrans = ex.Message;
Trace.WriteLine(ex.Message);
}
busy = false;
}
protected override async Task ReadFromDevice()
{
var SampleRate_var = await _model.Measurement.sampleRate.sampleRate.GetValue();
CurrentSampleRate = SampleRate_var * 1000;
/*****other code********/
}
public void ChangeSampleRate()
{
subscription?.Dispose();
Observable.Interval(TimeSpan.FromMilliseconds(CurrentSampleRate)).Subscribe(async t => await Refresh());
}
I have been trying to fetching details from device.On the start,i'll subscribe a event 1 sec,in the it try to read from device(ReadFromDevice) and i'll change get the CurrentSampleRate,Onproperty change,that time I'll dispose 1sec subscribe event and i will subscribe new event with current sample rate.But problem is ReadFromDevice happens at every 1 sec.
Try this:
var CurrentSampleRate = 30;
Observable
.Generate(0, x => true, x => x + 1, x => x, x => TimeSpan.FromMilliseconds(CurrentSampleRate))
.Subscribe(x =>
{
if (x == 50)
{
CurrentSampleRate = 1000;
}
Console.Write(".");
});
It's a little nasty in that you're changing some external state to make the observable's interval change - so you may need to use Interlocked.Exchange to make the update safe.
I have a probably simple question about the task factory. I have to following code:
In this task is a loop that is polling data from the RS232 and a counter that stops polling after 10 times. After this "doCollect" will be set to false.
And now comes the strange thing: The task runs repeatedly. The caller code is:
// class Main()
RS232DataAquisition _RS232DataAquisition = new RS232DataAquisition();
public override void Run()
{
System.Diagnostics.Stopwatch timeout = new System.Diagnostics.Stopwatch();
timeout.Start();
_RS232DataAquisition.Start();
while ((timeout.ElapsedMilliseconds <= (dataGatherTime_inSeconds * 1000)) && _RS232DataAquisition.DoCollect)
{
System.Threading.Thread.Sleep(100);
}
timeout.Stop();
_RS232DataAquisition.Stop();
}
Per my understanding the Run() function should start the thread and return into the while-loop waiting for the thread to finish. But it never does?!
Here's the code for ReadDataFromRS232:
// sealed class RS232DataAquisition
private bool doCollect = false;
public bool DoCollect
{
get { return doCollect; }
}
public void Start()
{
doCollect = true;
currentTask = System.Threading.Tasks.Task.Factory.StartNew(() =>
{
this.ReadDataFromRS232();
});
}
private void ReadDataFromRS232(int NumtoRead = 10)
{
var port = new System.IO.Ports.SerialPort(PortName);
int waitCount = 5;
var portExists = System.IO.Ports.SerialPort.GetPortNames().Any(x => x == PortName);
if (!portExists)
{
throw new ArgumentException("Port does not exist!");
}
while (port.IsOpen && waitCount-- > 0)
{
doCollect = false;
Wait();
}
doCollect = true;
if (!port.IsOpen)
{
port.Open();
port.NewLine = _NewLine;
port.ReadTimeout = 2000;
int number;
try { }
finally { }
port.Write("flashon\r");
while (doCollect && (_readCounter <= NumtoRead))
{
string s;
try
{
s = port.ReadLine();
}
catch
{
s = "-1";
}
int i;
if (int.TryParse(s, out i))
{
number = Convert.ToInt32(s, 10);
}
else
{
number = 0;
}
lock (thisLock) _data.Add(number);
_readCounter++;
}
port.Write("flashoff\r");
port.Close();
port.Dispose();
Wait(); Wait();
}
}
private void Wait()
{
System.Threading.Thread.Sleep(10);
System.Threading.Thread.SpinWait(1);
}
I don't get, why "ReadDataFromRS232" is beeing repeated until the timeout stops this task.
Thank you for any help :)
EDIT: Added some missing code.
As Dennis said the problem seemed to come from the missing volatile. It works now even though I have no idea why it didn't before.
In a simple form app I'm running a constant thread when the app starts. Upon its first iteration everything goes smoothly and the thread method "Thread_ContinousChecker" works as intended. After it's run once and the the lockChecker.returnBlock() == true hits then it does not run again. Ie, does not attempt again. I have a hunch that it is something to do with the await lockChecker.checkTime() line but don't understand why, if it works once why would it stop?
Note : It only stops working if the first if statement in the Thread_ContinousChecker method hits, ie if lockChecker.returnBlock() method is true. If it's false, it continues on.
Here is my program class
static class Program
{
//Instantiate the lockform
static LockForm lockForm;
public static bool checkLockForm()
{
Form checker = Application.OpenForms["LockForm"];
return (checker == null);
}
public static void toggleLockForm(bool theBool)
{
//If theBool (our condition) is true start the form
if (theBool == true)
{
//Checks if form already eixsts
if (checkLockForm() == true)
{
//Starts the form
Application.Run(lockForm = new LockForm());
}
}
//Now if theBool is false - we want to close down any instances of the form that we may have started
if (theBool == false)
{
//This is saying if an instance of a LockForm exists
if (checkLockForm() == false)
{
//Rest of app does not close but that lockform is disabled.
//lockForm.Close();
Application.Restart();
}
}
}
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
MyController cont = new MyController();
//Start new thread for our lock checking
Thread thread = new Thread(new ThreadStart(cont.Thread_ContinuousChecker));
thread.IsBackground = true;
thread.Name = "Data Polling Thread";
thread.Start();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new TrayApp());
}
public class MyController
{
public Boolean checkForm()
{
if (Process.GetProcessesByName("ControlApp.exe").Length > 0)
{
// Is running
return true;
}
if (Process.GetProcessesByName("ControlApp.exe").Length == 0)
{
// Is not running - so start it
return false;
}
return false;
}
public async void Thread_ContinuousChecker()
{
while (true)
{
if (checkForm() == false)
{
LockLogic lockChecker = new LockLogic();
await lockChecker.checkTime();
if (lockChecker.returnBlock() == true)
{
Program.toggleLockForm(true);
}
if (lockChecker.returnBlock() == false)
{
Program.toggleLockForm(false);
}
}
Thread.Sleep(10000);
}
}
}
Here is my LockLogic's .checkTime() method which I'm awaiting in the above Program class
public async Task checkTime()
{
// Read values back from Json file
var serializedList = await Task.Run(() => File.ReadAllText(_filePathTimes));
// getting a list of LockTime objects
var lockTimeList = await Task.Run(() => (List<LockTime>)JsonConvert.DeserializeObject(serializedList, typeof(List<LockTime>), new JsonSerializerSettings { MissingMemberHandling = MissingMemberHandling.Error }));
//
if (lockTimeList == null)
{
return;
}
if(lockTimeList.Count == 0)
{
return;
}
_lockTimes = lockTimeList;
//Then I do a foreach loop to go through every value in the start list and add the same located value to my listOfTimes (the list of LockTime objects with start and end)
for (int x = 0; x < _lockTimes.Count; x++)
{
TimeSpan start = new TimeSpan(_lockTimes[x].Start.Hour, _lockTimes[x].Start.Minute, _lockTimes[x].Start.Second);
TimeSpan end = new TimeSpan(_lockTimes[x].End.Hour, _lockTimes[x].End.Minute, _lockTimes[x].End.Second);
TimeSpan now = new TimeSpan(DateTime.Now.TimeOfDay.Hours, DateTime.Now.TimeOfDay.Minutes, DateTime.Now.TimeOfDay.Seconds);
if ((now > start) && (now < end))
{
_block = true;
}
else
{
_block = false;
}
}
}
A massive thanks to anyone who can spot what's going wrong.
I have a hunch that the problem is your use of Application.Run(lockForm = new LockForm());. As per http://msdn.microsoft.com/en-us/library/ms157902(v=vs.110).aspx, "This method adds an event handler to the mainForm parameter for the Closed event. The event handler calls ExitThread to clean up the application."
So, it's doing what you told it - binding the lifetime of the application to the lifetime of the newly created LockForm.
Hope this helps.