I tried to use delegate chain like below, trying to make animation in unity:
public class Class1
{
class Dele {
delegate void MyDelegate();
private MyDelegate dele;
private int count = 0;
public void Animate() {
dele = new MyDelegate(DoIe);
}
IEnumerator Ie() {
Debug.Log(count);
count += 1;
yield return new WaitForSeconds(5f);
}
private void DoIe() {
StartCouroutine(Ie());
for (int i=0; i<10; i++) {
dele += DoIe;
}
dele();
}
}
//call new Dele().Animate() here
}
I thought the Log will go like
1
(5 secs)
2
(5 secs)
...
10
but instead,
1
2
..
10
was logged at the same time.
If I want to callback another Ie after 5 second,
what should I do??
With coroutines it's the code inside the routine (the IEnumerator method) that runs later. The code after StartCoroutine() in your void-returning method above will run synchronously (straight away), like you saw.
You don't need a delegate here at all. All you need is this:
IEnumerator Ie() {
for (int i=0; i<10; i++) {
Debug.Log(count);
count += 1;
yield return new WaitForSeconds(5f);
}
}
private void DoIe() {
StartCoroutine(Ie());
}
First of all, your class needs to inherit from MonoBehavious for StartCoroutine to work.
Then in regards of your question: you need to start the coroutine with a delay, just adding them to a multicast delegate is simply not doing what you think you are
Related
This is using Unity3D. I have three coroutines: GetJSONFromSelectedSubreddit(), LoadMoreMemes(), and a function in a separate script that needs to be able to access the array of memes through the GetNewMemes() function (must return type Meme[]). LoadNewMemes() produces. The thing is, LoadMoreMemes() requires the json to work, so they have to run in the mentioned order. If you need the functions, here they are:
public void GetNewMemes(string subReddit, int count)
{
SetSelectedSubreddit(subReddit);
memesAtATime = count;
subJSON = null;
StartCoroutine(GetJSONFromSelectedSubreddit());
StartCoroutine(LoadMoreMemes());
}
IEnumerator GetJSONFromSelectedSubreddit()
{
gettingJSON = true;
WWW requester = new WWW("https://www.reddit.com/r/" + selectedSub + "/new.json?sort=new&count=25&after=" + postIndex);
yield return requester;
subJSON = requester.text;
json = new JSONObject(subJSON);
gettingJSON = false;
}
IEnumerator LoadMoreMemes()
{
while (gettingJSON)
yield return new WaitForSeconds(0.1f);
for (int i = 0; i < memesAtATime; i++)
{
yield return StartCoroutine(GetUserPostKarma(json["data"]["children"][i]["data"]["author"].str));
string sourceURL = json["data"]["children"][i]["data"]["preview"]["images"][0]["source"]["url"].str;
sourceURL = sourceURL.Replace("&", "&");
yield return StartCoroutine(GrabImage(sourceURL));
Meme currentMeme = new Meme(
json["data"]["children"][i]["data"]["preview"]["images"][0]["source"]["url"].str,
authorPostKarma,
(int) json["data"]["children"][i]["data"]["score"].i,
json["data"]["children"][i]["data"]["permalink"].str,
json["data"]["children"][i]["data"]["title"].str,
currentBitmap
);
Debug.Log(currentMeme.cost);
memes[i] = currentMeme;
}
}
Here's the other script:
void Start ()
{
RedditCommunicator redditCommunicator = GetComponent<RedditCommunicator>();
redditCommunicator.GetNewMemes("me_irl", 1);
Meme[] memes = redditCommunicator.GetCurrentMemes();
Debug.Log(memes[0].currentScore);
redditCommunicator.SpawnOneMeme(memes[0]);
}
Each function works fine on its own, but they need to wait for each other to finish, as well as run in the correct order to work. I'd like the functions to stay separate so I can call them individually in the future. memes is a private variable, and the one I'd like to pass to the other script calling these functions. If you don't think I've tried my options Googling and solving this on my own, just believe me, I've done my best. Thanks for your help in advance. If you need more information, just ask me for it. The current state of this code is it returns memes to early, before the coroutines can finish, resulting in empty memes.
You can yield a Coroutine in an IEnumerator which will halt the progression of that Coroutine until that Coroutine is done. Like this:
void Start()
{
StartCoroutine(DoThings((text) => {
Debug.Log("Dothings told me: " + text);
}));
}
IEnumerator DoThings(Action<string>() callback)
{
yield return StartCoroutine(DoThisFirst());
callback("Returning a value mid-method!");
yield return StartCoroutine(ThenThis());
Debug.Log(3);
}
IEnumerator DoThisFirst()
{
yield return new WaitForSeconds(1);
Debug.Log(1);
}
IEnumerator ThenThis()
{
yield return new WaitForSeconds(1);
Debug.Log(2);
}
Problem is that GetJSONFromSelectedSubreddit and LoadNewMemes methods are called as two "parallel" coroutines in GetNewMemes method.
If you do not need to run a coroutine "asynchronously", you can just enumerate through it:
public void GetNewMemes(string subReddit, int count)
{
SetSelectedSubreddit(subReddit);
memesAtATime = count;
subJSON = null;
var enumerator = GetJSONFromSelectedSubreddit();
while (enumerator.MoveNext());
enumerator = LoadNewMemes();
while (enumerator.MoveNext());
}
This question already has answers here:
Captured variable in a loop in C#
(10 answers)
Closed 6 years ago.
I am trying to pass an integer value into a ElapsedEventHandler so I can do logic based on the integer passed in. However, when the Event is raised, the value coming in is not the value I initialized it to. I may not be understanding completely how the delegate works.
class Example
{
Dictionary<int, Timer> timer;
public Example()
{
timer = new Dictionary<int, timer>();
for(int i = 0; i < 12; ++i)
{
timer.Add(i, new Timer(5000));
timer[i].Elapsed += delegate { TimerTickCustom(i); };
}
}
public void Process() // called each cycle
{
for(int i = 0; i < 12; ++i)
{
timer[i].Start();
}
}
private void TimerTickCustom(int i)
{
// The value of i coming in does not match the dictionary key.
}
}
It depends on where the local value i is for the delegate to regard it as "specific" to it's scope. As i is defined outside the loop, the delegate doesn't define it's own "copy" of this variable - as all the delegates expect the same i.
What you need to do is assign it to a variable that's "on the same level" as the delegate itself.
Not sure if I'm using the right language here to explain it. But this should work:
class Example
{
Dictionary<int, Timer> timer;
public Example()
{
timer = new Dictionary<int, Timer>();
for(int i = 0; i < 12; ++i)
{
int iInScopeOfDelegate = i;
timer.Add(i, new Timer(5000));
timer[i].Elapsed += delegate { TimerTickCustom(iLocalToDelegate ); };
}
}
public void Process() // called each cycle
{
for(int i = 0; i < 12; ++i)
{
timer[i].Start();
}
}
private void TimerTickCustom(int i)
{
// The value of i coming in does not match the dictionary key.
}
}
There's some interesting discussion if you know which words to type into the search engine (which of course, you can't really know until someone tells you).
I've got a class. It has a method that does a lot of work. I would like to not have the program hang while it does the work. I understand yield will do this for me.
void Start() {
DoWork(10,10);
}
void DoWork (int x, int y) {
for (int i=0; i < x; i++) {
for (int j=0; j < y; j++) {
// Stuff
}
}
}
If I add a yield like this
void Start() {
DoWork(10, 10);
}
IEnumerable DoWork (int x, int y) {
for (int i=0; i < x; i++) {
for (int j=0; j < y; j++) {
// Stuff
}
Debug.Log (1);
yield return null;
}
}
None of the work gets done and on top of that I see no log statements at all.
How do I yield my code so the program doesn't freeze?
This is Unity3D engine so your coroutine needs to return IEnumerator to be valid:
void Start() {
StartCoroutine(DoWork(10, 10));
}
IEnumerator DoWork (int x, int y) {
for (int i=0; i < x; i++) {
for (int j=0; j < y; j++) {
// Stuff
}
Debug.Log (1);
yield return null;
}
}
This is in no way multithreading. It is run just like an update once per frame between the Update and the LateUpdate except if you use
yield return new WaitForEndOfFrame();
then it is postponed until after the rendering process. What it does is create a new object of type Coroutine and place it on the calling MonoBehaviour stack of coroutines.
This works as a method that performs some repetitive action but always return to the main program when hitting a yield. It will then catch back from there on the next frame.
You need to use the StartCoroutine method:
void Start() {
StartCoroutine(DoWork(10, 10));
}
IEnumerator DoWork (int x, int y) {
// (A)
yield return null;
// (B)
for (int i=0; i < x; i++) {
for (int j=0; j < y; j++) {
// Stuff
}
Debug.Log (1);
yield return null;
// (C)
}
}
Yur code is executed piece by piece where delimiter of steps is the yield operator, i.e. when Framework calls MoveNext() the first time - the code (A) will be executed, when it calls MoveNext() second time - the code (B) will be executed, then code (C), and so on and so forth.
When you add a yield statement, the compiler actually generates a private class that acts as a state machine that implements IEnumerable. As such none of the code wrapped up from the original method will be called unless you enumerate the result of the method - in your example, you're throwing away the return value, so nothing would happen.
Yield keyword is used for lazy loading/computation support in C#.
Try doing:
var result = DoWork().ToList();
This forces an evaluation of the DoWork() method and you will see the logging taking place.
C# yield in Unity works just like C# yield always does. Unity does not influence this in any way.
yield is a keyword that is used to allow enumeration over a set of return values.
IEnumerator<int> MyEnumerationMethod()
{
yield return 5;
yield return 1;
yield return 9;
yield return 4;
}
void UserMethod1()
{
foreach (int retVal in MyEnumerationMethod())
Console.Write(retVal + ", ");
// this does print out 5, 1, 9, 4,
}
void UserMethod2()
{
IEnumerator<int> myEnumerator = MyEnumerationMethod();
while (myEnumerator.MoveNext())
Console.Write(myEnumerator.Current + ", ");
// this does print out 5, 1, 9, 4,
}
UserMethod1() and UserMethod2() are pretty much the same. UserMethod1() is just the C# syntactic sugar version of UserMethod2().
Unity uses this language feature to implement Coroutines:
When you call StartCoroutine() and pass it an IEnumerator, Unity stores this enumerator and calls MoveNext() for the first time. This will cause MyEnumerationMethod() to be called and executed up until the first yield return. At this point, MoveNext() returns and the first result (5) can be retrieved by looking at the Current property of the enumerator.
Now, Unity regularly checks the Current property and - depending on its value - decides whether the time has come to call MoveNext() again. The value of Current might be an instance of WaitForEndOfFrame, an instance of WWW or whatever, and depending on that the time, MoveNext() is called is decided.
Once MoveNext() is called again, execution of MyEnumerationMethod() will be continued at the point where it was interrupted last time, and executes until the next yield return is executed. And so on.
That's all there is to yield, and to Coroutines in Unity.
I am calling a service that only allows 10 calls per second. I am using Stopwatch and Thread.Sleep to limit my calls. Are these the correct tools for this job, or should I be using Timers or some other tool.
public void SomeFunction() {
Stopwatch stopwatch = new Stopwatch();
int numCallsThisSecond = 0;
foreach(MyEvent changedEvent in changedEvents) {
stopwatch.Start();
service.ChangeEvent(changedEvent);
numCallsThisSecond += 1;
stopwatch.Stop();
if(numCallsThisSecond==10 && stopwatch.ElapsedMilliseconds<=1000)
Thread.Sleep((int)(1100-stopwatch.ElapsedMilliseconds));
if(stopwatch.ElapsedMilliseconds>1000) {
stopwatch.Reset();
numCallsThisSecond = 0;
}
}
}
Thank you in advance for any help!
As you already know it can be 10 calls / sec. Code can be simple as follows :
public void SomeFunction()
{
foreach(MyEvent changedEvent in changedEvents)
{
service.ChangeEvent(changedEvent);
Thread.Sleep(100);//you already know it can be only 10 calls / sec
}
}
Edit :
Ok got it, please see if following alternative will be helpful, it only allows 10 or less calls per second depending on how its performing :
public void SomeFunction()
{
DateTime lastRunTime = DateTime.MinValue;
foreach(MyEvent changedEvent in changedEvents)
{
lastRunTime = DateTime.Now;
for (int index = 0; index < 10; index++)
{
if (lastRunTime.Second == DateTime.Now.Second)
{
service.ChangeEvent(changedEvent);
}
else
{
break;//it took longer than 1 sec for 10 calls, so break for next second
}
}
}
}
Example for threading queue book "Accelerated C# 2008" (CrudeThreadPool class) not work correctly. If I insert long job in WorkFunction() on 2-processor machine executing for next task don't run before first is over. How to solve this problem? I want to load the processor to 100 percent
public class CrudeThreadPool
{
static readonly int MAX_WORK_THREADS = 4;
static readonly int WAIT_TIMEOUT = 2000;
public delegate void WorkDelegate();
public CrudeThreadPool()
{
stop = 0;
workLock = new Object();
workQueue = new Queue();
threads = new Thread[MAX_WORK_THREADS];
for (int i = 0; i < MAX_WORK_THREADS; ++i)
{
threads[i] = new Thread(new ThreadStart(this.ThreadFunc));
threads[i].Start();
}
}
private void ThreadFunc()
{
lock (workLock)
{
int shouldStop = 0;
do
{
shouldStop = Interlocked.Exchange(ref stop, stop);
if (shouldStop == 0)
{
WorkDelegate workItem = null;
if (Monitor.Wait(workLock, WAIT_TIMEOUT))
{
// Process the item on the front of the queue
lock (workQueue)
{
workItem = (WorkDelegate)workQueue.Dequeue();
}
workItem();
}
}
} while (shouldStop == 0);
}
}
public void SubmitWorkItem(WorkDelegate item)
{
lock (workLock)
{
lock (workQueue)
{
workQueue.Enqueue(item);
}
Monitor.Pulse(workLock);
}
}
public void Shutdown()
{
Interlocked.Exchange(ref stop, 1);
}
private Queue workQueue;
private Object workLock;
private Thread[] threads;
private int stop;
}
public class EntryPoint
{
static void WorkFunction()
{
Console.WriteLine("WorkFunction() called on Thread 0}", Thread.CurrentThread.GetHashCode());
//some long job
double s = 0;
for (int i = 0; i < 100000000; i++)
s += Math.Sin(i);
}
static void Main()
{
CrudeThreadPool pool = new CrudeThreadPool();
for (int i = 0; i < 10; ++i)
{
pool.SubmitWorkItem(
new CrudeThreadPool.WorkDelegate(EntryPoint.WorkFunction));
}
pool.Shutdown();
}
}
I can see 2 problems:
Inside ThreadFunc() you take a lock(workLock) for the duration of the method, meaning your threadpool is no longer async.
in the Main() method, you close down the threadpool w/o waiting for it to finish. Oddly enough that is why it is working now, stopping each ThreadFunc after 1 loop.
It's hard to tell because there's no indentation, but it looks to me like it's executing the work item while still holding workLock - which is basically going to serialize all the work.
If at all possible, I suggest you start using the Parallel Extensions framework in .NET 4, which has obviously had rather more time spent on it. Otherwise, there's the existing thread pool in the framework, and there are other implementations around if you're willing to have a look. I have one in MiscUtil although I haven't looked at the code for quite a while - it's pretty primitive.