14

I have a long running method that must run on UI thread. (Devex - gridView.CopyToClipboard())

I do not need the UI to be responsive while copying and I added a splash screen so the user isn't bored out of his mind.

When I run this program all is well.

Trouble starts when I run a different program which in turn starts a new process and runs the program on it. After a few seconds of copying the title reads (Not Responding) and the mouse cursor shows busy, it of course clears up within a few seconds but I'd like to get rid of it since it gives the user the misconstrued feeling that the program is faulty.

Is there any way to set the "Time out" of a process I create?

EDIT:

The main program calls the following code:

fillsProcess = new Process();
fillsProcess.StartInfo.FileName = Application.ExecutablePath;
fillsProcess.Start();

In the fillsProcess, when a certain button is clicked the following code is called:

gridViewToCopy.CopyToClipboard();

This line of code takes a while to process and after a few seconds the window of the fillsProcess looks unresponsive since this method runs on the UI thread..

EDIT The 2nd:

Apparently (and really quite understandably)

gridViewToCopy.CopyToClipboard();

Is not the only method causing this problem. Many Devex methods have to run on UI thread (e.g. Sorting of data, Filtering of data)

So thanks to anyone who offered specific solution (that either worked or didn't) but my original question pops right back up again:

Is there any way to change the time-out time or somehow control the whole "Not Responding" fiasco?

E.T.
  • 949
  • 8
  • 21
  • Splash screen is the different program, wich starts when you start copying? Title of splash screen? – shibormot Feb 26 '13 at 17:21
  • this looks useful, more specifically Jeffrey Hantin's answer: http://stackoverflow.com/questions/1691251/what-makes-a-process-appear-as-not-responding-in-windows – James Feb 26 '13 at 17:22
  • You might want to take this up with Developer Express. – 500 - Internal Server Error Feb 26 '13 at 17:29
  • 12
    I can envision that DevExpress service request. Q: "My program looks broken when I copy ten thousand rows to the clipboard" A: "Your program is broken". – Hans Passant Feb 26 '13 at 19:02
  • Does `Application.DoEvents()` do anything? I know it's suppose to work but has never given me consistent results. – gunr2171 Feb 26 '13 at 19:52
  • @shibormot no. the splash screen was just to explain that the app is not supposed to b responsive. – E.T. Feb 27 '13 at 09:43
  • @judgeja I saw that before posting. I can't seem to understand why this happens **only** when another program starts a process that runs this program. – E.T. Feb 27 '13 at 09:44
  • @HansPassant I don't think this is a Devex issue since their component works ok. the problem seems to be processes and friends. – E.T. Feb 27 '13 at 09:45
  • @E.T.may be you need initialization of program (setting startup parameters stuff) be completed before you show its window – shibormot Feb 27 '13 at 09:49
  • @shibormot all is initialized and running. this event is triggered by a button... – E.T. Feb 27 '13 at 10:39
  • @E.T. some code will be helpful. and title of wich program is not responding? – shibormot Feb 27 '13 at 10:51
  • Try `Form.Enabled=false` before starting the copying. I guess that windows won't send any new messages to the window, and therefore it will not be 'Not responding' to nothing. You can try `Application.DoEvents` after that to finish all waiting messages. – RoadBump Feb 27 '13 at 10:57
  • @RoadBump thanks for the suggestion - but that didn't do the trick :( – E.T. Feb 27 '13 at 11:13
  • Have you tried collecting the messages for the app as suggested in that link? Forcing a call to wndProc would presumably do that right? – James Feb 27 '13 at 11:48
  • @judgeja Isn't that the whole point? Doesn't wndProc run on the UI thread rendering it unavailable during this time? even if I call it from a different thread there's no response... – E.T. Feb 27 '13 at 14:05
  • My understanding (could be completely off) was that wndProc runs on the UI thread when other things are idle, so in your case it's not getting a chance to run and get the messages. If you do a call to it in a reasonable way in your long running method could it not get the messages and then windows wouldn't show up your program as not responding? – James Feb 27 '13 at 14:23
  • @judgeja The method isn't mine.. it's a Devex method that I'm using. So I can't add a call to wndProc in the middle of it. – E.T. Feb 27 '13 at 14:46
  • ohhhhhh sorry! That makes this question even more interesting. – James Feb 27 '13 at 14:50
  • I find it very difficult to believe your app doesn't respond when spawned by another process but does respond when run on its own. Why do you think putting a "Time out" of a process you create will solve the not responding? – Jeremy Thompson Mar 10 '13 at 05:31
  • @JeremyThompson I know it's difficult to believe, but that's what's happening. And the problem happens because the program is busy for more than the timeout time, if you have any other suggestions feel free but I'm fresh out of ideas... – E.T. Mar 10 '13 at 08:04
  • from my point of view copying this number of records to the clipboard isn't good. Since you copy data frim the grid, may be it is better to copy data from underlying DB in the background thread. Also, what is the goal of putting this data to the clipboard? – platon Mar 12 '13 at 09:09
  • @platon I'm copying to clipboard so it can be pasted to Excel. I cannot use export to excel or any manipulation on excel since the App is run from a remote app :-/ – E.T. Mar 12 '13 at 10:21
  • @E.T., I would suggest that in this case you go the way I suggested. I.e. get all required rows IDs from the grid (this should be fast). Start a new thread, pass these IDs to a thread method where you can access the database and copy data to the clipboard. If you want, I can create an example on how this can be done.... – platon Mar 12 '13 at 10:54
  • @E.T., one more solution is to override the gridView's GetText() and GetSelectedData methods which are used by the GridView to collect data which is later passed to the Clipboard. This should also be an easy solution especially if you own the source code. – platon Mar 12 '13 at 11:01
  • @E.T. you seem to be using our controls not effectively. I sad ours because I work for DevExpress :). Changing such timeouts in any case results in changing the client environment which is not a good idea from my point of view. So, mt questions are: how many records shows the grid so that even sorting causes the application to hang? How many data are you passing to the clipboard? I think you should redesign the app if it is possible by using the so called server mode. Is it possible? – platon Mar 12 '13 at 17:17
  • this should address problems related to the sorting, grouping and so. Populating clipboard should be implemented in the background thread... – platon Mar 12 '13 at 17:23
  • @platon thanks. I will look into the server mode. I didn't think this will have anything to do with Devex. in the meantime I disabled the "Not responding" – E.T. Mar 13 '13 at 09:45

8 Answers8

17

You can use DisableProcessWindowsGhosting win32 function:

[DllImport("user32.dll")]
public static extern void DisableProcessWindowsGhosting();

This actually doesn't prevent the window from freezing, but prevents the "Not Respongind" text in the title.

Mohammad Dehghan
  • 17,853
  • 3
  • 55
  • 72
2

I am afraid the simplest solution is to make your own CopyToClipboard() where you in your for loop, every now and then, do an Application.DoEvents, which keeps the ui thread responsive.

I guess most licenses of DevExpress have the source code available, so you can probably copy paste most if it.

Since you know the data you can probably make a much simpler procedure than the generic that DevExpress uses.

like this:

const int feedbackinterval = 1000;

private void btnCopy_Click(object sender, EventArgs e)
{
    StringBuilder txt2CB = new StringBuilder();
    int[] rows = gridView1.GetSelectedRows();

    if (rows == null) return;

    for (int n = 0; n < rows.Length; n++)
    {
        if ((n % feedbackinterval) == 0) Application.DoEvents();

        if (!gridView1.IsGroupRow(rows[n]))
        {
            var item = gridView1.GetRow(rows[n]) as vWorkOrder;
            txt2CB.AppendLine(String.Format("{0}\t{1}\t{2}",
            item.GroupCode, item.GroupDesc, item.note_no??0));
        }
     }
        Clipboard.SetText(txt2CB.ToString());
}
Stig
  • 1,323
  • 16
  • 22
1

This is because you call a long running method synchronously in your main application thread. As your applicaton is busy it does not respond to messages from windows and is marked as (Not Responding) until finished.

To handle this do your copying asynchronously e.g. using a Task as one simplest solution.

Task task = new Task(() =>
        {
            gridView.Enabled = false;
            gridView.CopyToClipboard();
            gridView.Enabled = true;
        });

        task.Start();

Disable your grid so nobody can change values in the GUI. The rest of your application remains responsive (may has side effects!).

  • 1
    Please read the comments. Yhis approach has been suggeted but it doesn't help. – E.T. Feb 28 '13 at 18:34
  • 2
    Of course this will not help, since the calls to `set_Enable`, `CopyToClopboard` and again `set_Enable` are nothing else than windows messages when they are executed from another thread (Since the main thread must be STA for Windows Applications). The methods get executed inside the window thread, that cannot respond to other messages for as long as those three calls are pending. The result will be the same as described in the question. – Carsten Mar 06 '13 at 10:49
  • This code may throw Cross-thread UI exceptions. Its best to do the item above in a `this.BeginInvoke( () => { /* code here */ });` to stop that happening. – Dominic Zukiewicz Mar 12 '13 at 10:56
  • But if you call BeginInvoke on a control, the delegate is executed on the UI thread, so you're back to square one. – pixelbadger Mar 12 '13 at 15:53
1

You could start the process hidden and then check if responding and bring it back into view when complete....your splash screen would show its still "responding".

 Process proc = new Process();
 proc.StartInfo.FileName = "<Your Program>.exe"

 proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;

Edit: You could also create a Timer event watching the other process and roll your own timeout logic

    DateTime dStartTime = DateTime.Now;
    TimeSpan span = new TimeSpan(0, 0, 0);
    int timeout = 30; //30 seconds        

    private void timer1_Tick(Object myObject, EventArgs myEventArgs)
    {
        while (span.Seconds < timeout)
        {
            Process[] processList = Process.GetProcessesByName("<YourProcess.exe>");
            if (processList.Length == 0)
            {
                //process completed
                timer1.Stop();
                break;
            }
            span = DateTime.Now.Subtract(dStartTime);
        }
        if (span.Seconds > timeout)
        {
            Process[] processList = Process.GetProcessesByName("<YourProcess.exe>");

            //Give it one last chance to complete
            if (processList.Length != 0)
            {
                //process not completed
                foreach (Process p in processList)
                {
                    p.Kill();
                }
            }
            timer1.Stop();
        }
    }

Edit2

You could use pInvoke "ShowWindow" to accomplish hiding and showing the window too after its started

private const int SW_HIDE = 0x00;
private const int SW_SHOW = 0x05;

[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
jordanhill123
  • 4,142
  • 2
  • 31
  • 40
1

There are several possible approaches

  • Hide the main form for the period of the operation
  • Somehow clone/serialize the control and pass it to a thread with another UI dispatcher
  • Get the selected cells via gridView.GetSelectedCells() and then put their contents to the clipboard asynchronously

It would be more helpful if you've uploaded the GridView library somewhere, so that we could look inside.

Ark-kun
  • 6,358
  • 2
  • 34
  • 70
  • Thanks for the reply but see my edit. I can't really hide the grid evey time a user does an action :-/ – E.T. Mar 12 '13 at 15:27
1

I'm unclear whether your user needs to see the screen that is "unresponsive". If it unnecessary, you might try letting the application run in the background after the main thread for this app is closed; or you may minimize the app.

If it is necessary to see the application and for it to appear to be working, can you segment your "copy to clipboard" function such that is is threaded and takes in either an array or the gridview and an index range. The advantage to this is that your main thread on your subordinate process would never hang. The disadvantage is that people don't like to work with threading and delegates in C#.

Mike_Matthews_II
  • 722
  • 6
  • 15
  • Can't do that. as mentioned the method isn't mine and must run on UI thread. – E.T. Mar 12 '13 at 10:23
  • If I'm reading this correctly, your method's name is essential CopyGridToClipboard (or is it CopyGridToClipBoard). I'd roll my own, since it sounds like a really straightforward method; and once I rolled my own as a static method in a static class (or instanced), I'd throw it in a new thread. If the method does something not implied by the name you shared, that might be helpful information...though, it still sounds like you might enjoy the "roll your own" approach ;-) – Mike_Matthews_II Mar 12 '13 at 13:31
1

Okay, the 'Not Responding' and window artifacting you've described are just symptoms of running a long term activity on your UI thread. The UI thread is blocked, so the UI is frozen. There is no avoiding this. To be honest, it's just 'lucky' that your application appears as responsive as it does.

As far as I can see every workaround that has been described here is just a hack to fudge the fact that your UI thread is frozen. Don't do this. Fix your program so the UI thread isn't frozen.

Ask yourself: do my users really need to copy all the rows from this view? Can the data be filtered in some way to limit the rows? If not, there is a property called MaxRowCopyCount which limits the number of rows that are copied - can this be leveraged without breaking your workflow?

Finally, if all else fails, is there some other medium you can use (perhaps an intermediate file), to which data can be copied on a background thread?

pixelbadger
  • 1,556
  • 9
  • 24
0

The timeout, documented in IsHungAppWindow, cannot be changed. Don't use global state to manage a local problem.

You have to optimize the part that causes unresponsiveness. For example use caching, virtual grid (DevExpress calls it "server mode"), paging, delegate the sorting to an ibindinglistview filter that does a database query (uses database index) instead of in-memory sorting (no indexing) or implement IAsyncOperation on your clipboard data so you would only need to populate the data when the user does a paste.

Sheng Jiang 蒋晟
  • 15,125
  • 2
  • 28
  • 46