可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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?
回答1:
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.
回答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());
}
回答3:
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!).
回答4:
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);
回答5:
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.
回答6:
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#.
回答7:
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?
回答8:
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.