वर्तमान में मैं फ़ाइलों को स्थानांतरित करने के लिए डेल्फी XE3 क्लाइंट/सर्वर एप्लिकेशन पर काम कर रहा हूं (इंडी एफ़टीपी घटकों के साथ)। क्लाइंट भाग एक फ़ोल्डर की निगरानी करता है, अंदर फ़ाइलों की एक सूची प्राप्त करता है, उन्हें सर्वर पर अपलोड करता है और मूल को हटा देता है। अपलोडिंग एक अलग थ्रेड द्वारा की जाती है, जो फ़ाइलों को एक-एक करके संसाधित करती है। फाइलें 0 से कुछ हज़ार तक हो सकती हैं और उनके आकार भी बहुत भिन्न होते हैं।मल्टीथ्रेडेड फ़ाइल अपलोड सिंक्रनाइज़ेशन
यह ओएसएक्स और विंडोज दोनों के लिए संकलित एक फायरमोनकी ऐप है, इसलिए मुझे ओमनी थ्रेड लाइब्रेरी के बजाय टीटीएचड का उपयोग करना पड़ा, जिसे मैंने पसंद किया था। मेरा ग्राहक रिपोर्ट करता है कि एप्लिकेशन यादृच्छिक रूप से जमा हो जाता है। मैं इसे डुप्लिकेट नहीं कर सका, लेकिन चूंकि मेरे पास टीटीएचड के साथ इतना अनुभव नहीं है, इसलिए मैंने कहीं डेडलॉक स्थिति डाली होगी। मैंने काफी सारे उदाहरण पढ़े हैं, लेकिन मुझे अभी भी कुछ मल्टीथ्रेड विनिर्देशों के बारे में निश्चित नहीं है।
ऐप संरचना सरल है:
मुख्य धागे में एक टाइमर फ़ोल्डर की जांच करता है और प्रत्येक फ़ाइल के बारे में जानकारी रिकॉर्ड में प्राप्त करता है, जो एक सामान्य टीएलिस्ट में जाता है। यह सूची फाइलों, आकार, प्रगति के नामों के बारे में जानकारी रखती है, भले ही फ़ाइल पूरी तरह से अपलोड हो या फिर से प्रयास किया जाए। प्रगति सलाखों, आदि के साथ एक ग्रिड में प्रदर्शित सभी। यह सूची केवल मुख्य धागे द्वारा उपयोग की जाती है। उसके बाद सूची से आइटम को AddFile विधि (नीचे कोड) को कॉल करके थ्रेड पर भेजा जाता है। धागा सभी फ़ाइलों को एक थ्रेड-सुरक्षित कतार में संग्रहीत करता है जैसे http://delphihaven.wordpress.com/2011/05/06/using-tmonitor-2/
जब फ़ाइल अपलोड की जाती है तो अपलोडर थ्रेड सिंक्रनाइज़ करने के लिए कॉल के साथ मुख्य थ्रेड को सूचित करता है।
मुख्य थ्रेड समय-समय पर वर्तमान फ़ाइल प्रगति की जांच करने और इसे प्रदर्शित करने के लिए अपलोडर.गेटप्रकाश विधि को कॉल करता है। यह फ़ंक्शन वास्तव में थ्रेड-सुरक्षित नहीं है, लेकिन क्या यह डेडलॉक का कारण बन सकता है, या केवल गलत डेटा लौटाया जा सकता है?
प्रगति जांच करने के लिए एक सुरक्षित और कारगर तरीका क्या होगा?
तो क्या यह दृष्टिकोण ठीक है या मैंने कुछ याद किया है? आप यह कैसे करेंगे?
उदाहरण के लिए मैं फ़ोल्डर सामग्री को पढ़ने के लिए एक नया धागा बनाने के बावजूद। इसका मतलब है कि मैं जिस टीएलआईस्ट का उपयोग करता हूं उसे थ्रेड-सुरक्षित बनाया जाना चाहिए, लेकिन जीयूआई ग्रिड में प्रदर्शित जानकारी को रीफ्रेश करने के लिए इसे हर समय एक्सेस करना होगा। क्या सभी सिंक्रनाइज़ेशन सिर्फ जीयूआई को धीमा नहीं करेंगे?
यदि कोई इसे देखना चाहता है तो मैंने नीचे सरलीकृत कोड पोस्ट किया है। यदि नहीं, तो मुझे सामान्य रूप से उपयोग करने के बारे में कुछ राय सुनकर खुशी होगी। मुख्य लक्ष्य ओएसएक्स और विंडोज दोनों पर काम करना है; सभी फाइलों और वर्तमान की प्रगति के बारे में जानकारी प्रदर्शित करने में सक्षम होने के लिए; और फाइलों की संख्या और आकार के बावजूद उत्तरदायी होने के लिए।
यह अपलोडर धागा का कोड है। मैं आसानी से पढ़ने के लिए इसके बारे में कुछ निकाल दिया है: मुख्य थ्रेड कि अपलोड करने वाले के साथ बातचीत की
type
TFileStatus = (fsToBeQueued, fsUploaded, fsQueued);
TFileInfo = record
ID: Integer;
Path: String;
Size: Int64;
UploadedSize: Int64;
Status: TFileStatus;
end;
TUploader = class(TThread)
private
FTP: TIdFTP;
fQueue: TThreadedQueue<TFileInfo>;
fCurrentFile: TFileInfo;
FUploading: Boolean;
procedure ConnectFTP;
function UploadFile(aFileInfo: TFileInfo): String;
procedure OnFTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
procedure SignalComplete;
procedure SignalError(aError: String);
protected
procedure Execute; override;
public
property Uploading: Boolean read FUploading;
constructor Create;
destructor Destroy; override;
procedure Terminate;
procedure AddFile(const aFileInfo: TFileInfo);
function GetProgress: TFileInfo;
end;
procedure TUploader.AddFile(const aFileInfo: TFileInfo);
begin
fQueue.Enqueue(aFileInfo);
end;
procedure TUploader.ConnectFTP;
begin
...
FTP.Connect;
end;
constructor TUploader.Create;
begin
inherited Create(false);
FreeOnTerminate := false;
fQueue := TThreadedQueue<TFileInfo>.Create;
// Create the TIdFTP and set ports and other params
...
end;
destructor TUploader.Destroy;
begin
fQueue.Close;
fQueue.Free;
FTP.Free;
inherited;
end;
// Process the whole queue and inform the main thread of the progress
procedure TUploader.Execute;
var
Temp: TFileInfo;
begin
try
ConnectFTP;
except
on E: Exception do
SignalError(E.Message);
end;
// Use Peek instead of Dequeue, because the item should not be removed from the queue if it fails
while fQueue.Peek(fCurrentFile) = wrSignaled do
try
if UploadFile(fCurrentFile) = '' then
begin
fQueue.Dequeue(Temp); // Delete the item from the queue if succesful
SignalComplete;
end;
except
on E: Exception do
SignalError(E.Message);
end;
end;
// Return the current file's info to the main thread. Used to update the progress indicators
function TUploader.GetProgress: TFileInfo;
begin
Result := fCurrentFile;
end;
// Update the uploaded size for the current file. This information is retrieved by a timer from the main thread to update the progress bar
procedure TUploader.OnFTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
begin
fCurrentFile.UploadedSize := AWorkCount;
end;
procedure TUploader.SignalComplete;
begin
Synchronize(
procedure
begin
frmClientMain.OnCompleteFile(fCurrentFile);
end);
end;
procedure TUploader.SignalError(aError: String);
begin
try
FTP.Disconnect;
except
end;
if fQueue.Closed then
Exit;
Synchronize(
procedure
begin
frmClientMain.OnUploadError(aError);
end);
end;
// Clear the queue and terminate the thread
procedure TUploader.Terminate;
begin
fQueue.Close;
inherited;
end;
function TUploader.UploadFile(aFileInfo: TFileInfo): String;
begin
Result := 'Error';
try
if not FTP.Connected then
ConnectFTP;
FUploading := true;
FTP.Put(aFileInfo.Path, ExtractFileName(aFileInfo.Path));
Result := '';
finally
FUploading := false;
end;
end;
और भागों:
......
// Main form
fUniqueID: Integer; // This is a unique number given to each file, because there might be several with the same names(after one is uploaded and deleted)
fUploader: TUploader; // The uploader thread
fFiles: TList<TFileInfo>;
fCurrentFileName: String; // Used to display the progress
function IndexOfFile(aID: Integer): Integer; //Return the index of the record inside the fFiles given the file ID
public
procedure OnCompleteFile(aFileInfo: TFileInfo);
procedure OnUploadError(aError: String);
end;
// This is called by the uploader with Synchronize
procedure TfrmClientMain.OnUploadError(aError: String);
begin
// show and log the error
end;
// This is called by the uploader with Synchronize
procedure TfrmClientMain.OnCompleteFile(aFileInfo: TFileInfo);
var
I: Integer;
begin
I := IndexOfFile(aFileInfo.ID);
if (I >= 0) and (I < fFiles.Count) then
begin
aFileInfo.Status := fsUploaded;
aFileInfo.UploadedSize := aFileInfo.Size;
FFiles.Items[I] := aFileInfo;
Inc(FFilesUploaded);
TFile.Delete(aFileInfo.Path);
colProgressImg.UpdateCell(I);
end;
end;
procedure TfrmClientMain.ProcessFolder;
var
NewFiles: TStringDynArray;
I, J: Integer;
FileInfo: TFileInfo;
begin
// Remove completed files from the list if it contains more than XX files
while FFiles.Count > 1000 do
if FFiles[0].Status = fsUploaded then
begin
Dec(FFilesUploaded);
FFiles.Delete(0);
end else
Break;
NewFiles := TDirectory.GetFiles(WatchFolder, '*.*',TSearchOption.soAllDirectories);
for I := 0 to Length(NewFiles) - 1 do
begin
FileInfo.ID := FUniqueID;
Inc(FUniqueID);
FileInfo.Path := NewFiles[I];
FileInfo.Size := GetFileSizeByName(NewFiles[I]);
FileInfo.UploadedSize := 0;
FileInfo.Status := fsToBeQueued;
FFiles.Add(FileInfo);
if (I mod 100) = 0 then
begin
UpdateStatusLabel;
grFiles.RowCount := FFiles.Count;
Application.ProcessMessages;
if fUploader = nil then
break;
end;
end;
// Send the new files and resend failed to the uploader thread
for I := 0 to FFiles.Count - 1 do
if (FFiles[I].Status = fsToBeQueued) then
begin
if fUploader = nil then
Break;
FileInfo := FFiles[I];
FileInfo.Status := fsQueued;
FFiles[I] := FileInfo;
SaveDebug(1, 'Add: ' + ExtractFileName(FFiles[I].Path));
FUploader.AddFile(FFiles[I]);
end;
end;
procedure TfrmClientMain.tmrGUITimer(Sender: TObject);
var
FileInfo: TFileInfo;
I: Integer;
begin
if (fUploader = nil) or not fUploader.Uploading then
Exit;
FileInfo := fUploader.GetProgress;
I := IndexOfFile(FileInfo.ID);
if (I >= 0) and (I < fFiles.Count) then
begin
fFiles.Items[I] := FileInfo;
fCurrentFileName := ExtractFileName(FileInfo.Path);
colProgressImg.UpdateCell(I);
end;
end;
function TfrmClientMain.IndexOfFile(aID: Integer): Integer;
var
I: Integer;
begin
Result := -1;
for I := 0 to FFiles.Count - 1 do
if FFiles[I].ID = aID then
Exit(I);
end;
मुझे यकीन नहीं है और आपने परीक्षण नहीं किया है .. लेकिन क्या आपने एक TIdAntiFreeze जोड़ने की कोशिश की और जांच की कि क्या व्यवहार समान है? (FMX.IdAntiFreeze) – Whiler
TIdAntiFreeze को मुख्य थ्रेड (उदा। फॉर्म पर गिराए गए) से इंडी घटक का उपयोग करते समय जीयूआई को ठंडा करने से रोकने के लिए डिज़ाइन किया गया है। मैं इसे एक अलग थ्रेड में उपयोग करता हूं इसलिए मुझे नहीं लगता कि इससे कैसे मदद मिलेगी। कम से कम जहां तक मुझे पता है ... – VGeorgiev
पहली बार देखो, आपकी त्रुटि हैंडलिंग मेरे लिए गलत लगती है। उदाहरण के लिए, निष्पादन विधि में, यदि कनेक्टफ़टीपी कॉल विफल हो जाता है, तो आप अपवाद _ त्रुटि (त्रुटि के बारे में सूचित करने के बाद), और आप अभी भी अपलोडफाइल पर कॉल जारी करते हैं। IMHO आपको _clean_ करना होगा, और थ्रेड को FatalException के साथ मरने दें या निष्पादन विधि के अंदर अपवाद को सही तरीके से संभाल लें, उदाहरण के लिए, कनेक्शन को फिर से प्रयास करना, शायद त्रुटि के प्रकार के आधार पर। दूसरी ओर, यदि आपके पास मुख्य धागे में एक सूची है, तो मैं यह देखने में असफल रहा कि आपको व्यक्तिगत धागे में कतार की आवश्यकता क्यों है। – jachguate